Given a string s
and an integer k
, you are asked to find the length of the longest substring of s
that contains at most k
distinct characters.
In other words, you want the length of the longest continuous segment of s
where there are no more than k
different characters present.
s
and an integer k
Constraints:
0 ≤ k ≤ 26
1 ≤ |s| ≤ 105
s
consists of English lettersAt first glance, it might seem natural to consider all possible substrings and count the number of distinct characters in each. However, this brute-force approach would be extremely slow, especially for long strings.
Instead, we look for a way to efficiently "slide" through the string, keeping track of the characters within a current window, and expanding or shrinking this window as needed. This leads us to the sliding window technique.
The key insight is: As we move through the string, we can efficiently maintain a count of characters within the current window and adjust the window's size to always satisfy the "at most k
distinct characters" constraint.
We use the sliding window algorithm with two pointers (left and right) and a hash map (or dictionary) to track the count of each character within the window.
left
and right
, both at the start of the string. Create a hash map to count occurrences of each character in the current window. Set max_len
to 0.
right
forward one character at a time, adding the character to the hash map and updating its count.
k
, shrink the window from the left by moving left
forward and updating the hash map accordingly (decrement the count, remove the character if its count reaches 0).
max_len
to be the maximum of its current value and the window size (right - left + 1
).
right
reaches the end of the string.
We use a hash map because it allows us to quickly check how many distinct characters are in the current window (the size of the map), and to efficiently update character counts as the window moves.
Example: s = "eceba"
, k = 2
left = 0
, right = 0
, window = "e"
, map = {e:1}, distinct = 1 (valid), max_len = 1
right
to 1: window = "ec"
, map = {e:1, c:1}, distinct = 2 (valid), max_len = 2
right
to 2: window = "ece"
, map = {e:2, c:1}, distinct = 2 (valid), max_len = 3
right
to 3: window = "eceb"
, map = {e:2, c:1, b:1}, distinct = 3 (not valid)
"eb"
, left = 2, right = 3max_len
remains 3
right
to 4: window = "eba"
, map = {e:1, b:1, a:1}, distinct = 3 (not valid)
"ba"
, left = 3, right = 4max_len
remains 3
The longest substring with at most 2 distinct characters is "ece"
with length 3.
Brute-force approach: For every possible substring, count the number of distinct characters. This takes O(N2) substrings, and for each, O(N) to count distinct characters, so total O(N3).
Optimized (Sliding Window) approach:
This problem is a classic example of using the sliding window technique to efficiently handle substring constraints. By tracking character counts in a hash map and dynamically adjusting the window, we can solve the problem in linear time. The approach is both elegant and practical for large inputs, making it a staple technique for substring and subarray problems with constraints.
from collections import defaultdict
class Solution:
def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
if k == 0 or not s:
return 0
left = 0
max_len = 0
char_map = defaultdict(int)
for right, char in enumerate(s):
char_map[char] += 1
while len(char_map) > k:
char_map[s[left]] -= 1
if char_map[s[left]] == 0:
del char_map[s[left]]
left += 1
max_len = max(max_len, right - left + 1)
return max_len
#include <unordered_map>
#include <string>
using namespace std;
class Solution {
public:
int lengthOfLongestSubstringKDistinct(string s, int k) {
if (k == 0 || s.empty()) return 0;
unordered_map<char, int> char_map;
int left = 0, max_len = 0;
for (int right = 0; right < s.size(); ++right) {
char_map[s[right]]++;
while (char_map.size() > k) {
char_map[s[left]]--;
if (char_map[s[left]] == 0) {
char_map.erase(s[left]);
}
left++;
}
max_len = max(max_len, right - left + 1);
}
return max_len;
}
};
import java.util.HashMap;
class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
if (k == 0 || s.length() == 0) return 0;
HashMap<Character, Integer> map = new HashMap<>();
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
map.put(c, map.getOrDefault(c, 0) + 1);
while (map.size() > k) {
char leftChar = s.charAt(left);
map.put(leftChar, map.get(leftChar) - 1);
if (map.get(leftChar) == 0) {
map.remove(leftChar);
}
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
}
function lengthOfLongestSubstringKDistinct(s, k) {
if (k === 0 || s.length === 0) return 0;
const map = new Map();
let left = 0, maxLen = 0;
for (let right = 0; right < s.length; right++) {
map.set(s[right], (map.get(s[right]) || 0) + 1);
while (map.size > k) {
map.set(s[left], map.get(s[left]) - 1);
if (map.get(s[left]) === 0) {
map.delete(s[left]);
}
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}