1100. Find K-Length Substrings With No Repeated Characters 🔒
Problem Description
You are given a string s
and an integer k
. Your task is to find how many substrings of length k
exist in the string s
where all characters in the substring are unique (no repeated characters).
A substring is a contiguous sequence of characters within the string. For example, in the string "abc", the substrings of length 2 are "ab" and "bc".
The key requirement is that each substring must:
- Have exactly
k
characters - Contain no duplicate characters (all characters must be different)
For instance, if s = "havefunonleetcode"
and k = 5
, you would check all consecutive 5-character windows in the string. The substring "havef" has all unique characters, so it counts. The substring "avefu" also has all unique characters. But a substring like "eeton" would not count because it contains two 'e' characters.
The function should return the total count of such valid substrings.
Intuition
The core insight is that we need to check every possible substring of length k
in the string. A naive approach would be to examine each substring individually, checking if it has all unique characters. However, this would involve redundant work since consecutive substrings overlap significantly.
Consider two consecutive substrings of length k
: one starting at position i
and another at position i+1
. These substrings share k-1
characters. The only difference is that the second substring loses the first character of the previous window and gains one new character at the end.
This observation naturally leads to a sliding window approach. We can maintain a window of size k
and slide it across the string one position at a time. Instead of rechecking all k
characters each time, we only need to:
- Remove the character that's leaving the window (the leftmost character)
- Add the character that's entering the window (the new rightmost character)
To efficiently track whether all characters in our window are unique, we can use a hash table (or Counter in Python) that stores the frequency of each character in the current window. The key realization is that if the hash table has exactly k
entries, it means all k
characters in the window are different (since each unique character would have its own entry).
When we slide the window:
- We increment the count for the new character entering
- We decrement the count for the character leaving
- If a character's count drops to 0, we remove it from the hash table entirely
This way, we can check if the current window has all unique characters in constant time by simply checking if len(cnt) == k
.
Learn more about Sliding Window patterns.
Solution Approach
We implement a sliding window technique combined with a hash table to efficiently count valid substrings.
Initial Window Setup:
First, we create a Counter (hash table) cnt
to store the frequency of characters in our current window. We initialize it with the first k
characters of the string: cnt = Counter(s[:k])
.
We then check if this initial window has all unique characters by verifying if len(cnt) == k
. If the hash table has exactly k
entries, it means all characters are different, so we initialize our answer ans
accordingly: ans = int(len(cnt) == k)
.
Sliding the Window:
We iterate through the string starting from index k
to the end. For each position i
:
-
Add the new character: We add the character entering the window
s[i]
to our counter:cnt[s[i]] += 1
-
Remove the old character: We decrement the count of the character leaving the window
s[i - k]
:cnt[s[i - k]] -= 1
-
Clean up the hash table: If the count of the leaving character becomes 0, we remove it entirely from the hash table:
if cnt[s[i - k]] == 0: cnt.pop(s[i - k])
-
Check validity: After updating the window, we check if all characters in the current window are unique by verifying if
len(cnt) == k
. If true, we increment our answer:ans += int(len(cnt) == k)
Why this works:
The hash table size equals k
only when we have exactly k
different characters, each appearing once. If any character appears more than once, the hash table would have fewer than k
entries (since multiple occurrences of the same character share one entry with a count greater than 1).
Time and Space Complexity:
- Time:
O(n)
wheren
is the length of strings
, as we visit each character once - Space:
O(k)
for the hash table, which stores at mostk
different characters
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 walk through the solution with s = "abcabc"
and k = 3
.
Step 1: Initialize the window with first k characters
- Initial window:
"abc"
(indices 0-2) - Create Counter:
cnt = {'a': 1, 'b': 1, 'c': 1}
- Check if valid:
len(cnt) = 3 = k
✓ (all characters are unique) - Initialize
ans = 1
Step 2: Slide window to position 3
- New character entering:
s[3] = 'a'
- Character leaving:
s[0] = 'a'
- Add new character:
cnt['a'] = 1 + 1 = 2
- Current counter:
cnt = {'a': 2, 'b': 1, 'c': 1}
- Remove old character:
cnt['a'] = 2 - 1 = 1
- Final counter:
cnt = {'a': 1, 'b': 1, 'c': 1}
- Current window:
"bca"
(indices 1-3) - Check if valid:
len(cnt) = 3 = k
✓ - Update
ans = 2
Step 3: Slide window to position 4
- New character entering:
s[4] = 'b'
- Character leaving:
s[1] = 'b'
- Add new character:
cnt['b'] = 1 + 1 = 2
- Current counter:
cnt = {'a': 1, 'b': 2, 'c': 1}
- Remove old character:
cnt['b'] = 2 - 1 = 1
- Final counter:
cnt = {'a': 1, 'b': 1, 'c': 1}
- Current window:
"cab"
(indices 2-4) - Check if valid:
len(cnt) = 3 = k
✓ - Update
ans = 3
Step 4: Slide window to position 5
- New character entering:
s[5] = 'c'
- Character leaving:
s[2] = 'c'
- Add new character:
cnt['c'] = 1 + 1 = 2
- Current counter:
cnt = {'a': 1, 'b': 1, 'c': 2}
- Remove old character:
cnt['c'] = 2 - 1 = 1
- Final counter:
cnt = {'a': 1, 'b': 1, 'c': 1}
- Current window:
"abc"
(indices 3-5) - Check if valid:
len(cnt) = 3 = k
✓ - Update
ans = 4
Final Result: 4 valid substrings
All four substrings of length 3 have unique characters: "abc"
, "bca"
, "cab"
, "abc"
.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
5 # Initialize counter for the first window of size k
6 char_count = Counter(s[:k])
7
8 # Check if the first window has all unique characters (no repeats)
9 # If the counter has k distinct characters, all are unique
10 result = int(len(char_count) == k)
11
12 # Slide the window through the rest of the string
13 for i in range(k, len(s)):
14 # Add the new character entering the window
15 char_count[s[i]] += 1
16
17 # Remove the character leaving the window
18 char_count[s[i - k]] -= 1
19
20 # If count becomes 0, remove the character from counter entirely
21 if char_count[s[i - k]] == 0:
22 char_count.pop(s[i - k])
23
24 # Check if current window has all unique characters
25 # Increment result if all k characters are distinct
26 result += int(len(char_count) == k)
27
28 return result
29
1class Solution {
2 public int numKLenSubstrNoRepeats(String s, int k) {
3 int stringLength = s.length();
4
5 // If string is shorter than k, no valid substring exists
6 if (stringLength < k) {
7 return 0;
8 }
9
10 // HashMap to track character frequencies in current window
11 Map<Character, Integer> charFrequencyMap = new HashMap<>(k);
12
13 // Initialize the first window of size k
14 for (int i = 0; i < k; i++) {
15 char currentChar = s.charAt(i);
16 charFrequencyMap.merge(currentChar, 1, Integer::sum);
17 }
18
19 // Check if first window has all unique characters
20 // (map size equals k means all k characters are unique)
21 int validSubstringCount = charFrequencyMap.size() == k ? 1 : 0;
22
23 // Slide the window through the rest of the string
24 for (int rightIndex = k; rightIndex < stringLength; rightIndex++) {
25 // Add new character to the window (right side)
26 char newChar = s.charAt(rightIndex);
27 charFrequencyMap.merge(newChar, 1, Integer::sum);
28
29 // Remove the leftmost character from the window
30 char charToRemove = s.charAt(rightIndex - k);
31 int newFrequency = charFrequencyMap.merge(charToRemove, -1, Integer::sum);
32
33 // If frequency becomes 0, remove the character from map entirely
34 if (newFrequency == 0) {
35 charFrequencyMap.remove(charToRemove);
36 }
37
38 // Check if current window has all unique characters
39 if (charFrequencyMap.size() == k) {
40 validSubstringCount++;
41 }
42 }
43
44 return validSubstringCount;
45 }
46}
47
1class Solution {
2public:
3 int numKLenSubstrNoRepeats(string s, int k) {
4 int stringLength = s.size();
5
6 // If string is shorter than k, no k-length substring exists
7 if (stringLength < k) {
8 return 0;
9 }
10
11 // Hash map to store character frequency in current window
12 unordered_map<char, int> charFrequency;
13
14 // Initialize the first window of size k
15 for (int i = 0; i < k; ++i) {
16 ++charFrequency[s[i]];
17 }
18
19 // Check if first window has all unique characters (map size equals k)
20 int count = (charFrequency.size() == k) ? 1 : 0;
21
22 // Slide the window through the rest of the string
23 for (int i = k; i < stringLength; ++i) {
24 // Add the new character entering the window
25 ++charFrequency[s[i]];
26
27 // Remove the character leaving the window
28 --charFrequency[s[i - k]];
29
30 // If frequency becomes 0, remove the character from map
31 if (charFrequency[s[i - k]] == 0) {
32 charFrequency.erase(s[i - k]);
33 }
34
35 // If current window has all unique characters, increment count
36 if (charFrequency.size() == k) {
37 ++count;
38 }
39 }
40
41 return count;
42 }
43};
44
1/**
2 * Counts the number of substrings of length k with no repeating characters
3 * @param s - The input string to search for substrings
4 * @param k - The length of substrings to find
5 * @returns The count of valid substrings with no repeating characters
6 */
7function numKLenSubstrNoRepeats(s: string, k: number): number {
8 const stringLength: number = s.length;
9
10 // If string is shorter than k, no valid substring exists
11 if (stringLength < k) {
12 return 0;
13 }
14
15 // Map to track character frequencies in current window
16 const charFrequencyMap: Map<string, number> = new Map<string, number>();
17
18 // Initialize the first window of size k
19 for (let i = 0; i < k; i++) {
20 const currentChar: string = s[i];
21 charFrequencyMap.set(currentChar, (charFrequencyMap.get(currentChar) ?? 0) + 1);
22 }
23
24 // Check if first window has all unique characters (map size equals k)
25 let validSubstringCount: number = charFrequencyMap.size === k ? 1 : 0;
26
27 // Slide the window through the rest of the string
28 for (let i = k; i < stringLength; i++) {
29 const incomingChar: string = s[i];
30 const outgoingChar: string = s[i - k];
31
32 // Add the new character to the window
33 charFrequencyMap.set(incomingChar, (charFrequencyMap.get(incomingChar) ?? 0) + 1);
34
35 // Remove the leftmost character from the window
36 charFrequencyMap.set(outgoingChar, (charFrequencyMap.get(outgoingChar) ?? 0) - 1);
37
38 // If character count becomes 0, remove it from the map
39 if (charFrequencyMap.get(outgoingChar) === 0) {
40 charFrequencyMap.delete(outgoingChar);
41 }
42
43 // If map size equals k, all characters in window are unique
44 validSubstringCount += charFrequencyMap.size === k ? 1 : 0;
45 }
46
47 return validSubstringCount;
48}
49
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm initializes a counter for the first k
characters, which takes O(k)
time. Then it performs a sliding window operation that iterates through the remaining n - k
characters. For each iteration, it performs constant-time operations: adding one character to the counter, decrementing the count of another character, potentially removing an element from the counter, and checking the counter's size. Since each character is processed exactly once after the initial window setup, the total time complexity is O(k) + O(n - k) = O(n)
.
Space Complexity: O(min(k, |Σ|))
, where |Σ|
is the size of the character set.
The counter stores at most k
distinct characters (the size of the window), but it's also bounded by the total number of possible distinct characters in the string. Since the problem uses lowercase English letters, |Σ| = 26
. Therefore, the counter can have at most min(k, 26)
entries at any given time, resulting in a space complexity of O(min(k, |Σ|))
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Edge Cases Where k > len(s)
One of the most common mistakes is not checking if k
is larger than the length of the string. When k > len(s)
, it's impossible to have any substring of length k
, and the code would crash when trying to initialize the counter with s[:k]
.
Problematic Code:
def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
char_count = Counter(s[:k]) # This fails if k > len(s)
# rest of the code...
Solution: Add an early return check at the beginning of the function:
def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
if k > len(s):
return 0
char_count = Counter(s[:k])
# rest of the code...
2. Incorrectly Checking for Unique Characters
A subtle mistake is checking if the counter values are all equal to 1 instead of checking the counter's size. This leads to unnecessary iteration through the counter.
Inefficient Approach:
# Wrong: Checking all values in counter
result += int(all(count == 1 for count in char_count.values()))
Correct Approach:
# Right: Simply check the size of the counter
result += int(len(char_count) == k)
The correct approach works because if any character appears more than once, the counter will have fewer than k
keys (multiple occurrences share the same key with count > 1).
3. Forgetting to Remove Zero-Count Characters
Failing to remove characters with zero count from the counter leads to incorrect results, as these "ghost" entries affect the length check.
Incorrect Code:
for i in range(k, len(s)):
char_count[s[i]] += 1
char_count[s[i - k]] -= 1
# Missing: removal of zero-count entries
result += int(len(char_count) == k) # This will be wrong!
Correct Code:
for i in range(k, len(s)):
char_count[s[i]] += 1
char_count[s[i - k]] -= 1
# Essential: Remove character if count becomes 0
if char_count[s[i - k]] == 0:
char_count.pop(s[i - k])
result += int(len(char_count) == k)
4. Using Wrong Window Boundaries
A common indexing error is using incorrect boundaries when sliding the window, such as s[i - k + 1]
instead of s[i - k]
.
Wrong:
char_count[s[i - k + 1]] -= 1 # This keeps wrong character in window
Correct:
char_count[s[i - k]] -= 1 # Removes the character that's k positions behind
5. Not Considering k = 0 or k = 1 Cases
While k = 0
might not be a valid input in most problems, k = 1
is a special case where every single character forms a valid substring (since a single character is always unique).
Enhanced Solution with All Edge Cases:
def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
# Handle edge cases
if k > len(s) or k <= 0:
return 0
if k == 1:
return len(s)
# Main sliding window logic
char_count = Counter(s[:k])
result = int(len(char_count) == k)
for i in range(k, len(s)):
char_count[s[i]] += 1
char_count[s[i - k]] -= 1
if char_count[s[i - k]] == 0:
char_count.pop(s[i - k])
result += int(len(char_count) == k)
return result
Which of the following problems can be solved with backtracking (select multiple)
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!