Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sliding Window - Used to efficiently maintain a window of k characters while tracking character uniqueness
  • Hash Map / Frequency Array - Used to track character counts within the current window
  • String Manipulation - Understanding how to iterate through substrings and track character frequencies

1. Brute Force

Intuition

The most direct approach is to examine every substring of length k and check if it contains all unique characters. For each starting position, we scan k characters and track their frequencies. If any character appears more than once, we stop early and move to the next substring.

Algorithm

  1. If k > 26, return 0 immediately since there are only 26 lowercase letters.
  2. For each starting index i from 0 to n - k:
    • Initialize a frequency array of size 26.
    • Iterate through the next k characters.
    • Increment the frequency of each character.
    • If any frequency exceeds 1, break out of the inner loop (duplicate found).
    • If we complete the inner loop without duplicates, increment the answer.
  3. Return the count of valid substrings.
class Solution:
    def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
        if k > 26:
            return 0
        answer = 0
        n = len(s)

        for i in range(n - k + 1):
            # Initializing an empty frequency array
            freq = [0] * 26

            for j in range(i, i + k):
                curr_char = ord(s[j]) - ord("a")

                # Incrementing the frequency of current character
                freq[curr_char] += 1

                # If a repeated character is found, we stop the loop
                if freq[curr_char] > 1:
                    break
            else:
                # If the substring does not have any repeated characters,
                # we increment the answer
                answer += 1

        return answer

Time & Space Complexity

  • Time complexity: O(nmin(m,k))O(n \cdot \min(m, k))
  • Space complexity: O(m)O(m)

Where nn is the length of s, kk is the given substring length, and mm is the number of unique characters allowed in the string. In this case, m=26m=26.


2. Sliding Window

Intuition

Instead of recomputing character frequencies for each substring from scratch, we can maintain a sliding window. As we move the window, we add the new character on the right and remove the character that falls off on the left. When a duplicate is detected, we shrink the window from the left until all characters are unique again.

Algorithm

  1. If k > 26, return 0 (impossible to have k unique characters).
  2. Initialize two pointers left = 0 and right = 0, and a frequency array.
  3. While right < n:
    • Add s[right] to the frequency array.
    • While the frequency of s[right] exceeds 1, remove s[left] from the window and increment left.
    • If the window size equals k, increment the answer, then shrink the window from the left by one to prepare for the next position.
    • Move right forward.
  4. Return the answer.
class Solution:
    def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
        # We can reuse the condition from the first approach
        # as for k > 26, there can be no substrings with only unique characters
        if k > 26:
            return 0
        answer = 0
        n = len(s)

        # Initializing the left and right pointers
        left = right = 0

        # Initializing an empty frequency array
        freq = [0] * 26

        # Function to obtain the index of a character according to the alphabet
        def get_val(ch: str) -> int:
            return ord(ch) - ord("a")

        while right < n:

            # Add the current character in the frequency array
            freq[get_val(s[right])] += 1

            # If the current character appears more than once in the frequency array
            # keep contracting the window and removing characters from the
            # frequency array till the frequency of the current character becomes 1.
            while freq[get_val(s[right])] > 1:
                freq[get_val(s[left])] -= 1
                left += 1

            # Check if the length of the current unique substring is equal to k
            if right - left + 1 == k:
                answer += 1

                # Contract the window and remove the leftmost character from the
                # frequency array
                freq[get_val(s[left])] -= 1
                left += 1

            # Expand the window
            right += 1

        return answer

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(m)O(m)

Where nn is the length of s and mm is the number of unique characters allowed in the string. In this case, m=26m=26.


Common Pitfalls

Not Handling k Greater Than 26

Since the string contains only lowercase English letters, it's impossible to have a substring of length greater than 26 with all unique characters. Forgetting to add an early return for k > 26 leads to unnecessary computation or incorrect results when the problem guarantees such substrings cannot exist.

Incorrect Window Shrinking Logic

In the sliding window approach, when a duplicate character is found, you must shrink the window from the left until the duplicate is removed. A common mistake is shrinking by exactly one position or shrinking until the window size is less than k. The correct approach is to shrink until the frequency of the newly added character becomes 1.

Off-by-One Errors in Loop Bounds

When iterating through possible starting positions for substrings of length k, the loop should run from 0 to n - k (inclusive). Using n - k + 1 as the upper bound or forgetting the inclusive boundary leads to either missing valid substrings or accessing indices out of bounds.