public class Solution {
public int LengthOfLongestSubstring(string s) {
var window = new HashSet<char>();
int best = 0;
int left = 0;
for (int right = 0; right < s.Length; right++){
while (window.Contains(s[right])) {
window.Remove(s[left]);
left++;
}
window.Add(s[right]);
best = Math.Max(best, right - left + 1);
}
return best;
}
}Optimal → sliding window with last-seen index, time, space
In the HashSet version, on seeing a duplicate we do removal in a while loop, right?
That loop is just walking left forward until it’s one position past the old copy of the duplicate. If we already know where that old copy is, we can set left to that position + 1 directly.
So we store, for each character, the index where we last saw it.
public class Solution {
public int LengthOfLongestSubstring(string s) {
var lastIndex = new Dictionary<char, int>();
int best = 0;
int left = 0;
for (int right = 0; right < s.Length; right++) {
char c = s[right];
if (lastIndex.ContainsKey(c) && lastIndex[c] >= left){
left = lastIndex[c] + 1;
}
lastIndex[c] = right;
best = Math.Max(best, right - left + 1);
}
return best;
}
}This is better than solution 1 because it does less operations, and jumps left straight.
We have >= left check here, because the dictionary remembers characters even after they’ve left the window, so a stored index smaller than left is stale and must be ignored.