Problem Overview
- We are given a string
s. - Our goal is to find the longest substring that does not contain repeating characters.
- In other words, the length of the substring should equal the number of unique characters in it.
- If found → return the length, otherwise → return
0. - There can be multiple valid substring of length
LeetCode - Longest Substring Without Repeating Characters
A substring is continous part of string. The string itself is a valid substring.
Example
Input: s = "abcabcbb"
Output: 3
Explanation: "abc" is the longest substring without repeating characters.
Brute Force
Intuition
- We try to generate all possible substrings.
- Use a hashmap to keep track of the count of characters seen in the current substring.
- If a character is encountered twice, reject that substring and move on.
- We keep track of length of an valid sub string.
Code
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
int result = 0;
unordered_map<char, int> mp;
int len = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (mp.count(s[j])) {
break;
}
len++;
mp[s[j]]++;
result = max(result, len);
}
mp.clear();
len = 0;
}
return result;
}
};
Complexity
- Time: O(n²) — two nested loops over the string
- Space: O(n)
Optimal Solution — Sliding Window
Intuition
- Instead of checking all combinations like brute force, we can reduce it using a sliding window.
- We start from index
0with both pointersiandj. - We move
jforward while checking if any element is repeated in the map. - If a repeated element is found, we move
iforward while decrementing the cout of that number in the map. - At every step when
jmoves ahead, we check for the maximum window length.
This problem is combination of Sliding Window and Hashmap.
Edge Case
When the value of char in map becomes zero we remove it, because the mp.count(s[j]) return true, even though character is not present.
Code
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
int result = 0;
unordered_map<char, int> mp;
int i = 0, j = 0;
while (i < n && j < n) {
if (mp.count(s[j])) {
mp[s[i]]--;
if (mp[s[i]] == 0) {
mp.erase(s[i]);
}
i++;
continue;
}
mp[s[j]]++;
j++;
result = max(result, (j - i));
}
return result;
}
};
Further Optimized Version
What We Improve
Index Storage Instead of Count
- Instead of storing the count of each character, store the last seen index.
- When a repeated character is found, jump
idirectly tomax(i, lastIndex + 1). - This avoids unnecessarily shrinking the window one step at a time.
Eliminates Redundant Shrinking
- In the previous approach,
imoves one step per iteration even when it could skip ahead. - With index storage, we skip directly past the duplicate — reducing iterations significantly.
- In the previous approach,
Optimized Code
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
int result = 0;
unordered_map<char, int> mp;
int i = 0, j = 0;
while (i < n && j < n) {
if (mp.count(s[j]))
i = max(i, mp[s[j]] + 1);
mp[s[j]] = j;
j++;
result = max(result, (j - i));
}
return result;
}
};
Time and Space Complexity
Time: O(n)
- Each character is visited at most twice (once by
j, skipped byi). - With index optimization, even fewer operations in practice.
- Each character is visited at most twice (once by
Space: O(n)
Final Takeaways
- Always break the problem into smaller sub problems.
- In sliding window, always think about when to expand and when to shrink the window.
- In two pointer you need to figure out when to move both points in same or opposite direction.
- Storing the last index instead of count is a powerful trick to avoid redundant pointer movement.
- String + unique characters → Think Sliding Window + Hashmap first.
