Facebook Pixel

1876. Substrings of Size Three with Distinct Characters

EasyHash TableStringCountingSliding Window
Leetcode Link

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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 substrings
  • mask: 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 number y
    • Removing it from the mask using XOR: mask ^= 1 << y
    • Moving the left boundary: l += 1
  • 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 using ans += int(r - l + 1 >= 3)

Bitmask Operations Explained:

  • 1 << x: Creates a number with only the x-th bit set
  • mask >> x & 1: Checks if the x-th bit in mask is 1
  • mask |= 1 << x: Sets the x-th bit to 1 (adds character)
  • mask ^= 1 << y: Flips the y-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 Evaluator

Example 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More