The answer has a monotonic property: if we can find a valid substring of length L, then there must also exist valid substrings of all lengths less than L. This makes binary search applicable. We binary search on the answer length and for each candidate length, use a fixed-size sliding window to check if any window of that size has at most k distinct characters.
k >= n, return n (entire string is valid).left = k and right = n.mid, check if a valid window of size mid exists:k, return true.left as the final answer.class Solution:
def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
n = len(s)
if k >= n:
return n
left, right = k, n
def isValid(size):
counter = collections.Counter(s[:size])
if len(counter) <= k:
return True
for i in range(size, n):
counter[s[i]] += 1
counter[s[i - size]] -= 1
if counter[s[i - size]] == 0:
del counter[s[i - size]]
if len(counter) <= k:
return True
return False
while left < right:
mid = (left + right + 1) // 2
if isValid(mid):
left = mid
else:
right = mid - 1
return leftWhere is the length of the input string
sand is the maximum number of distinct characters.
A variable-size sliding window is the natural fit for this problem. We expand the window by moving the right pointer and adding characters. When we exceed k distinct characters, we shrink the window from the left until we're back to at most k distinct characters. The maximum window size seen during this process is our answer.
left = 0, maxSize = 0, and a hash map counter to track character frequencies.right from 0 to n-1:s[right] to the counter.k:s[left].0, remove it from the map.left.maxSize = max(maxSize, right - left + 1).maxSize.class Solution:
def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
n = len(s)
max_size = 0
counter = collections.Counter()
left = 0
for right in range(n):
counter[s[right]] += 1
while len(counter) > k:
counter[s[left]] -= 1
if counter[s[left]] == 0:
del counter[s[left]]
left += 1
max_size = max(max_size, right - left + 1)
return max_sizeWhere is the length of the input string
sand is the maximum number of distinct characters.
An optimization on the standard sliding window: instead of shrinking the window with a while loop, we only shrink by one step when invalid. This keeps the window size from ever decreasing by more than one, which means we only need to track when the window grows. The final answer is the maximum window size achieved, which equals n - left at the end.
maxSize = 0 and a hash map counter.right from 0 to n-1:s[right] to the counter.k:s[right - maxSize] (the leftmost character of current max window).0.maxSize.maxSize.class Solution:
def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
max_size = 0
counter = collections.Counter()
for right in range(len(s)):
counter[s[right]] += 1
if len(counter) <= k:
max_size += 1
else:
counter[s[right - max_size]] -= 1
if counter[s[right - max_size]] == 0:
del counter[s[right - max_size]]
return max_sizeWhere is the length of the input string
sand is the maximum number of distinct characters.
When shrinking the window and decrementing character counts, failing to remove characters with zero count from the hash map leads to incorrect distinct character counts. Always delete or remove entries when their count reaches zero to maintain an accurate count of distinct characters in the current window.
A frequent mistake is calculating the window size as right - left instead of right - left + 1. Since both left and right are inclusive indices, the correct window size includes both endpoints. This off-by-one error leads to returning a result that is one less than the actual answer.
When k is zero, no characters are allowed in the substring, so the answer should be 0. Some solutions forget to handle this edge case, leading to incorrect results or infinite loops when the while condition never terminates because the window can never be valid.