1876. Substrings of Size Three with Distinct Characters
Problem Description
You need to find how many substrings of length exactly 3 in a given string s
contain no repeated characters.
A substring is considered "good" if all its characters are unique (no character appears more than once). You're specifically looking for good substrings that have exactly 3 characters.
For example:
- In the string "xyzzaz", the substring "xyz" is good (all characters are different)
- The substring "yzz" is not good (the character 'z' appears twice)
The task is to count all possible good substrings of length 3 in the string. If the same good substring appears multiple times at different positions, each occurrence should be counted separately.
Key points:
- A substring must be contiguous (consecutive characters from the original string)
- The substring must have exactly 3 characters
- All 3 characters in the substring must be different from each other
- Return the total count of such substrings
Intuition
The key insight is that we can use a sliding window to efficiently track unique characters. Instead of checking every possible substring of length 3 separately, we can maintain a window that always contains only unique characters.
Think of it this way: as we move through the string, we want to keep track of the longest stretch of unique characters ending at each position. If at position r
we have at least 3 unique characters in our current window, then the substring of length 3 ending at position r
is "good".
The clever part is using a bitmask to track which characters are in our window. Since we only have lowercase letters (26 possible characters), we can use a single integer where each bit represents whether a specific character is present. For example, if bit 0 is set, it means 'a' is in the window; if bit 1 is set, 'b' is in the window, and so on.
When we encounter a character that's already in our window (its bit is already set in the mask), we need to shrink the window from the left side. We keep removing characters from the left until the duplicate is removed. This ensures our window always contains unique characters.
At each position, after maintaining the window of unique characters, we check if the window size is at least 3. If it is, we've found a good substring of length 3 ending at the current position. The expression r - l + 1 >= 3
checks this condition, where r
is the right boundary and l
is the left boundary of our window.
This approach is efficient because we visit each character at most twice - once when adding it to the window and once when removing it, giving us linear time complexity.
Learn more about Sliding Window patterns.
Solution Approach
We implement a sliding window approach with a bitmask to track unique characters in the window.
Step 1: Initialize Variables
ans
: Counter for good substringsmask
: Bitmask to track which characters are in the window (initially 0)l
: Left boundary of the sliding window (initially 0)
Step 2: Process Each Character
We iterate through the string with index r
and convert each character to a number x
(0 for 'a', 1 for 'b', etc.) using ord(c) - 97
.
Step 3: Handle Duplicates
Before adding the current character to the window, we check if it's already present using mask >> x & 1
:
- If the
x
-th bit is 1, the character is already in the window - We shrink the window from the left by:
- Getting the character at position
l
and converting it to numbery
- Removing it from the mask using XOR:
mask ^= 1 << y
- Moving the left boundary:
l += 1
- Getting the character at position
- This continues until the duplicate is removed
Step 4: Add Current Character After ensuring no duplicates, we add the current character to the window:
- Set the
x
-th bit in the mask:mask |= 1 << x
Step 5: Count Good Substrings
If the window size r - l + 1
is at least 3, we have a good substring of length 3 ending at position r
:
- We increment
ans
by 1 usingans += int(r - l + 1 >= 3)
Bitmask Operations Explained:
1 << x
: Creates a number with only thex
-th bit setmask >> x & 1
: Checks if thex
-th bit in mask is 1mask |= 1 << x
: Sets thex
-th bit to 1 (adds character)mask ^= 1 << y
: Flips they
-th bit (removes character)
The algorithm ensures that at each position, we maintain the longest window of unique characters ending at that position. If this window has at least 3 characters, we've found a good substring of length 3.
Time Complexity: O(n) where n is the length of string s, as each character is visited at most twice.
Space Complexity: O(1) as we only use a fixed amount of extra space regardless of input size.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the string "aababcabc"
to count good substrings of length 3.
Initial Setup:
ans = 0
(count of good substrings)mask = 0
(binary: 000000, no characters in window)l = 0
(left boundary)
Step-by-step execution:
r = 0, character = 'a':
- Convert 'a' to x = 0
- Check if 'a' is in window:
mask >> 0 & 1 = 0
(not present) - Add 'a':
mask = 000001
(bit 0 set) - Window = "a", size = 1 (< 3)
ans = 0
r = 1, character = 'a':
- Convert 'a' to x = 0
- Check if 'a' is in window:
mask >> 0 & 1 = 1
(present!) - Remove characters from left until 'a' is gone:
- Remove s[0]='a':
mask ^= 1 << 0
, mask becomes 000000 - Move left:
l = 1
- Remove s[0]='a':
- Add current 'a':
mask = 000001
- Window = "a", size = 1 (< 3)
ans = 0
r = 2, character = 'b':
- Convert 'b' to x = 1
- Check if 'b' is in window:
mask >> 1 & 1 = 0
(not present) - Add 'b':
mask = 000011
(bits 0,1 set) - Window = "ab", size = 2 (< 3)
ans = 0
r = 3, character = 'a':
- Convert 'a' to x = 0
- Check if 'a' is in window:
mask >> 0 & 1 = 1
(present!) - Remove characters from left until 'a' is gone:
- Remove s[1]='a':
mask ^= 1 << 0
, mask becomes 000010 - Move left:
l = 2
- Remove s[1]='a':
- Add current 'a':
mask = 000011
- Window = "ba", size = 2 (< 3)
ans = 0
r = 4, character = 'b':
- Convert 'b' to x = 1
- Check if 'b' is in window:
mask >> 1 & 1 = 1
(present!) - Remove characters from left until 'b' is gone:
- Remove s[2]='b':
mask ^= 1 << 1
, mask becomes 000001 - Move left:
l = 3
- Remove s[2]='b':
- Add current 'b':
mask = 000011
- Window = "ab", size = 2 (< 3)
ans = 0
r = 5, character = 'c':
- Convert 'c' to x = 2
- Check if 'c' is in window:
mask >> 2 & 1 = 0
(not present) - Add 'c':
mask = 000111
(bits 0,1,2 set) - Window = "abc", size = 3 (= 3) ✓
- Found good substring "abc"!
ans = 1
r = 6, character = 'a':
- Convert 'a' to x = 0
- Check if 'a' is in window:
mask >> 0 & 1 = 1
(present!) - Remove characters from left until 'a' is gone:
- Remove s[3]='a':
mask ^= 1 << 0
, mask becomes 000110 - Move left:
l = 4
- Remove s[3]='a':
- Add current 'a':
mask = 000111
- Window = "bca", size = 3 (= 3) ✓
- Found good substring "bca"!
ans = 2
r = 7, character = 'b':
- Convert 'b' to x = 1
- Check if 'b' is in window:
mask >> 1 & 1 = 1
(present!) - Remove characters from left until 'b' is gone:
- Remove s[4]='b':
mask ^= 1 << 1
, mask becomes 000101 - Move left:
l = 5
- Remove s[4]='b':
- Add current 'b':
mask = 000111
- Window = "cab", size = 3 (= 3) ✓
- Found good substring "cab"!
ans = 3
r = 8, character = 'c':
- Convert 'c' to x = 2
- Check if 'c' is in window:
mask >> 2 & 1 = 1
(present!) - Remove characters from left until 'c' is gone:
- Remove s[5]='c':
mask ^= 1 << 2
, mask becomes 000011 - Move left:
l = 6
- Remove s[5]='c':
- Add current 'c':
mask = 000111
- Window = "abc", size = 3 (= 3) ✓
- Found good substring "abc"!
ans = 4
Final Result: The string "aababcabc" contains 4 good substrings of length 3: "abc" (at position 3-5), "bca" (at position 4-6), "cab" (at position 5-7), and "abc" (at position 6-8).
Solution Implementation
1class Solution:
2 def countGoodSubstrings(self, s: str) -> int:
3 # Initialize count of good substrings, bitmask for tracking characters, and left pointer
4 count = 0
5 char_bitmask = 0
6 left = 0
7
8 # Iterate through string with right pointer
9 for right in range(len(s)):
10 # Convert character to 0-based index (a=0, b=1, ..., z=25)
11 char_index = ord(s[right]) - ord('a')
12
13 # If current character already exists in window, shrink from left
14 while (char_bitmask >> char_index) & 1:
15 # Remove leftmost character from bitmask
16 left_char_index = ord(s[left]) - ord('a')
17 char_bitmask ^= (1 << left_char_index)
18 left += 1
19
20 # Add current character to bitmask
21 char_bitmask |= (1 << char_index)
22
23 # If window size is at least 3, we found a good substring
24 # (window has all unique characters and length >= 3)
25 if right - left + 1 >= 3:
26 count += 1
27
28 return count
29
1class Solution {
2 public int countGoodSubstrings(String s) {
3 int count = 0;
4 int length = s.length();
5
6 // Sliding window approach with bit manipulation
7 int left = 0;
8 int right = 0;
9 int bitMask = 0; // Tracks which characters are in current window
10
11 while (right < length) {
12 // Get the current character's position (0-25 for 'a'-'z')
13 int currentCharIndex = s.charAt(right) - 'a';
14
15 // Shrink window from left while current character already exists
16 while ((bitMask >> currentCharIndex & 1) == 1) {
17 // Remove leftmost character from window
18 int leftCharIndex = s.charAt(left) - 'a';
19 left++;
20 // Toggle off the bit for removed character
21 bitMask ^= 1 << leftCharIndex;
22 }
23
24 // Add current character to window by setting its bit
25 bitMask |= 1 << currentCharIndex;
26
27 // Check if current window has exactly 3 distinct characters
28 // Window size is (right - left + 1)
29 if (right - left + 1 >= 3) {
30 count++;
31 }
32
33 right++;
34 }
35
36 return count;
37 }
38}
39
1class Solution {
2public:
3 int countGoodSubstrings(string s) {
4 int goodSubstringCount = 0;
5 int stringLength = s.length();
6
7 // Sliding window approach with bit mask to track unique characters
8 int left = 0; // Left pointer of sliding window
9 int right = 0; // Right pointer of sliding window
10 int characterBitMask = 0; // Bit mask to track which characters are in current window
11
12 // Expand window by moving right pointer
13 for (right = 0; right < stringLength; ++right) {
14 int currentCharIndex = s[right] - 'a'; // Convert character to index (0-25)
15
16 // Shrink window from left while current character already exists in window
17 while ((characterBitMask >> currentCharIndex & 1) == 1) {
18 int leftCharIndex = s[left] - 'a';
19 characterBitMask ^= (1 << leftCharIndex); // Remove left character from bit mask
20 left++;
21 }
22
23 // Add current character to the window
24 characterBitMask |= (1 << currentCharIndex);
25
26 // Count if current window has at least 3 characters (good substring)
27 int windowSize = right - left + 1;
28 if (windowSize >= 3) {
29 goodSubstringCount++;
30 }
31 }
32
33 return goodSubstringCount;
34 }
35};
36
1/**
2 * Counts the number of substrings of length 3 with all distinct characters
3 * @param s - The input string to analyze
4 * @returns The count of good substrings (substrings of length 3 with no repeating characters)
5 */
6function countGoodSubstrings(s: string): number {
7 let count: number = 0;
8 const stringLength: number = s.length;
9
10 // Use sliding window with two pointers and a bitmask to track characters
11 let leftPointer: number = 0;
12 let rightPointer: number = 0;
13 let characterMask: number = 0; // Bitmask to track which characters are in current window
14
15 while (rightPointer < stringLength) {
16 // Get the character index (0-25 for 'a'-'z') at right pointer
17 const rightCharIndex: number = s.charCodeAt(rightPointer) - 'a'.charCodeAt(0);
18
19 // If character already exists in window, shrink from left until it's removed
20 while ((characterMask >> rightCharIndex) & 1) {
21 const leftCharIndex: number = s.charCodeAt(leftPointer) - 'a'.charCodeAt(0);
22 // Remove left character from mask using XOR
23 characterMask ^= 1 << leftCharIndex;
24 leftPointer++;
25 }
26
27 // Add current right character to the mask
28 characterMask |= 1 << rightCharIndex;
29
30 // If current window size is exactly 3, we found a valid substring
31 const windowSize: number = rightPointer - leftPointer + 1;
32 if (windowSize >= 3) {
33 count++;
34 }
35
36 rightPointer++;
37 }
38
39 return count;
40}
41
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of string s
.
The algorithm uses a sliding window approach with two pointers (l
and r
). The right pointer r
iterates through each character exactly once. The left pointer l
moves forward only when there's a duplicate character in the current window. In the worst case, each character can cause the left pointer to move at most once throughout the entire execution. Therefore, both pointers combined traverse the string at most 2n
times, resulting in O(n)
time complexity.
Space Complexity: O(1)
The algorithm uses a fixed amount of extra space:
mask
: An integer used as a bitmask to track which characters are present in the current window. Since there are only 26 lowercase letters, this requires constant space.ans
,l
,r
,x
,y
: Integer variables that use constant space.
The bitmask approach stores character presence information in a single integer regardless of input size, making the space complexity constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Misunderstanding the Window Size Logic
The current solution has a critical flaw - it counts ALL windows of size 3 or greater, not just windows of exactly size 3. When right - left + 1 >= 3
, the code increments the counter, but this doesn't guarantee we're counting a substring of exactly length 3.
Why this is wrong:
- If we have a window "abcd" (size 4), the condition
right - left + 1 >= 3
is true - The code would count this as a good substring, but we need substrings of EXACTLY length 3
- We should only be counting "abc", "bcd" etc., not considering the entire window of size 4
Example where this fails: For string "abcde":
- When we reach index 2 (character 'c'), window is "abc" - count should increase by 1
- When we reach index 3 (character 'd'), window is "abcd" - but this counts the window of size 4, not a substring of size 3
- When we reach index 4 (character 'e'), window is "abcde" - again counting window of size 5
Solution: Check for Exactly 3 Characters
Approach 1: Simple Sliding Window
class Solution:
def countGoodSubstrings(self, s: str) -> int:
if len(s) < 3:
return 0
count = 0
# Check each substring of length 3
for i in range(len(s) - 2):
# Get substring of exactly 3 characters
substring = s[i:i+3]
# Check if all characters are unique
if len(set(substring)) == 3:
count += 1
return count
Approach 2: Optimized with Character Tracking
class Solution:
def countGoodSubstrings(self, s: str) -> int:
if len(s) < 3:
return 0
count = 0
# Check each consecutive triplet
for i in range(len(s) - 2):
# Check if all three characters are different
if s[i] != s[i+1] and s[i+1] != s[i+2] and s[i] != s[i+2]:
count += 1
return count
The key insight is that for "good substrings of length exactly 3", we need to check each consecutive triplet individually, not maintain a variable-size window of unique characters.
How does merge sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!