Skip to content

Commit 859a5dc

Browse files
committed
feat(algorithms, sliding-window): length of longest substring
1 parent 8d3fbb9 commit 859a5dc

30 files changed

Lines changed: 167 additions & 58 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Longest Substring Without Repeating Characters
2+
3+
Given a string s, find the length of the longest substring without repeating characters.
4+
5+
## Examples
6+
7+
Example 1:
8+
9+
Input: s = "abcabcbb"
10+
Output: 3
11+
Explanation: The answer is "abc", with the length of 3.
12+
Example 2:
13+
14+
Input: s = "bbbbb"
15+
Output: 1
16+
Explanation: The answer is "b", with the length of 1.
17+
Example 3:
18+
19+
Input: s = "pwwkew"
20+
Output: 3
21+
Explanation: The answer is "wke", with the length of 3.
22+
Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
23+
Example 4:
24+
25+
Input: s = ""
26+
Output: 0
27+
28+
## Solution
29+
30+
This solution uses a variable-length sliding window to consider all substrings without repeating characters, and returns
31+
the length of the longest one at the end.
32+
We represent the state of the current window with a dictionary state which maps each character to the number of times it
33+
appears in the window.
34+
35+
![Solution 1](./images/solutions/length_longest_substring_without_repeating_characters_solution_1.png)
36+
![Solution 2](./images/solutions/length_longest_substring_without_repeating_characters_solution_2.png)
37+
38+
We use a for-loop to increment end to repeatedly expand the window. Each time we expand the window, we first increment
39+
the count of s[end] in state. As long as the character at end is not a duplicate character in the window, we can compare
40+
the length of the current window to the longest window we've seen so far, and continue expanding. The character at end
41+
is a duplicate if state[s[end]] > 1.
42+
43+
![Solution 3](./images/solutions/length_longest_substring_without_repeating_characters_solution_3.png)
44+
![Solution 4](./images/solutions/length_longest_substring_without_repeating_characters_solution_4.png)
45+
![Solution 5](./images/solutions/length_longest_substring_without_repeating_characters_solution_5.png)
46+
![Solution 6](./images/solutions/length_longest_substring_without_repeating_characters_solution_6.png)
47+
![Solution 7](./images/solutions/length_longest_substring_without_repeating_characters_solution_7.png)
48+
![Solution 8](./images/solutions/length_longest_substring_without_repeating_characters_solution_8.png)
49+
![Solution 9](./images/solutions/length_longest_substring_without_repeating_characters_solution_9.png)
50+
51+
At this point, we have to contract the window because it contains more than one g. We do this by removing the leftmost
52+
character (start += 1) from the window, and decrementing its count in state until there is only one g left in the window.
53+
54+
![Solution 10](./images/solutions/length_longest_substring_without_repeating_characters_solution_10.png)
55+
![Solution 11](./images/solutions/length_longest_substring_without_repeating_characters_solution_11.png)
56+
57+
At this point, our window is valid again, and we can continue this process of expanding and contracting the window until
58+
end reaches the end of the string.
59+
60+
![Solution 12](./images/solutions/length_longest_substring_without_repeating_characters_solution_12.png)
61+
![Solution 13](./images/solutions/length_longest_substring_without_repeating_characters_solution_13.png)
62+
![Solution 14](./images/solutions/length_longest_substring_without_repeating_characters_solution_14.png)
63+
![Solution 15](./images/solutions/length_longest_substring_without_repeating_characters_solution_15.png)
64+
![Solution 16](./images/solutions/length_longest_substring_without_repeating_characters_solution_16.png)
65+
![Solution 17](./images/solutions/length_longest_substring_without_repeating_characters_solution_17.png)
66+
![Solution 18](./images/solutions/length_longest_substring_without_repeating_characters_solution_18.png)
67+
![Solution 19](./images/solutions/length_longest_substring_without_repeating_characters_solution_19.png)
68+
![Solution 20](./images/solutions/length_longest_substring_without_repeating_characters_solution_20.png)
69+
![Solution 21](./images/solutions/length_longest_substring_without_repeating_characters_solution_21.png)
70+
![Solution 22](./images/solutions/length_longest_substring_without_repeating_characters_solution_22.png)
71+
![Solution 23](./images/solutions/length_longest_substring_without_repeating_characters_solution_23.png)
72+
![Solution 24](./images/solutions/length_longest_substring_without_repeating_characters_solution_24.png)
73+
![Solution 25](./images/solutions/length_longest_substring_without_repeating_characters_solution_25.png)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from typing import Dict
2+
3+
4+
def length_of_longest_substring(s: str) -> int:
5+
"""
6+
The most obvious way to do this would be to go through all possible substrings of the string which would result in
7+
an algorithm with an overall O(n^2) complexity.
8+
9+
But we can solve this problem using a more subtle method that does it with one linear traversal( O(n) complexity).
10+
11+
First, we go through the string one by one until we reach a repeated character. For example, if the string is
12+
“abcdcedf”, what happens when you reach the second appearance of "c"?
13+
14+
When a repeated character is found(let’s say at index j), it means that the current substring (excluding the
15+
repeated character of course) is a potential maximum, so we update the maximum if necessary. It also means that the
16+
repeated character must have appeared before at an index i, where i<j
17+
18+
Since all substrings that start before or at index i would be less than the current maximum, we can safely start
19+
to look for the next substring with head which starts exactly at index i+1.
20+
"""
21+
# creating a dictionary to store last positions of occurrence
22+
seen: Dict[str, int] = {}
23+
max_length = 0
24+
# staring the initial window at 0
25+
start = 0
26+
27+
for end, ch in enumerate(s):
28+
# have we seen this element already?
29+
if ch in seen:
30+
# last_index is the previous appearance of character 'ch'
31+
last_index = seen[ch]
32+
# move the start pointer to position after the last occurrence
33+
# We use max() to ensure start never moves backward (the previous occurrence might be before the current
34+
# window)
35+
start = max(start, last_index + 1)
36+
37+
# update last seen value of character
38+
seen[ch] = end
39+
max_length = max(max_length, end - start + 1)
40+
41+
return max_length
42+
43+
44+
def length_of_longest_substring_2(s: str) -> int:
45+
"""
46+
Another variation of checking the length of the longest substring in a provided string
47+
"""
48+
# creating a dictionary to store last positions of occurrence
49+
seen: Dict[str, int] = {}
50+
max_length = 0
51+
# staring the initial window at 0
52+
start = 0
53+
54+
for end, ch in enumerate(s):
55+
seen[ch] = seen.get(ch, 0) + 1
56+
while seen[ch] > 1:
57+
seen[s[start]] -= 1
58+
start += 1
59+
max_length = max(max_length, end - start + 1)
60+
61+
return max_length
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)