Skip to content

Commit 257b706

Browse files
committed
Problem: Longest Substring with Same Letters after Replacement
1 parent d5880ef commit 257b706

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Problem: Longest Substring with Same Letters after Replacement
2+
3+
LeetCode problem: [424. Longest Repeating Character Replacement](https://leetcode.com/problems/longest-repeating-character-replacement/).
4+
5+
Given a string with lowercase letters only, if you are allowed to replace no more than `K` letters with any letter, find the length of the longest substring having the same letters after replacement.
6+
7+
## Examples
8+
9+
Example 1:
10+
11+
```plaintext
12+
Input: String = "aabccbb", k = 2
13+
Output: 5
14+
Explanation: Replace the two 'c' with 'b' to have a longest repeating substring "bbbbb".
15+
```
16+
17+
Example 2:
18+
19+
```plaintext
20+
Input: String = "abbcb", k = 1
21+
Output: 4
22+
Explanation: Replace the 'c' with 'b' to have a longest repeating substring "bbbb".
23+
```
24+
25+
Example 3:
26+
27+
```plaintext
28+
Input: String = "abccde", k = 1
29+
Output: 3
30+
Explanation: Replace the 'b' or 'd' with 'c' to have the longest repeating substring "ccc".
31+
```
32+
33+
## Solution 1
34+
35+
The algorithm uses a dynamic-sized sliding window with greedy character replacement.
36+
37+
1. The algorithm maintains a dynamic-sized window that expands by moving the end of the window to the right.
38+
2. As the window expands, it tracks the frequency of characters within the window to determine the most frequent character.
39+
3. The number of replacements needed is calculated by: `(window size) - (frequency of the most frequent character)`. This gives the number of characters that must be changed to make the substring consists of one character.
40+
4. If the number of character replacements exceeds `K`, the window is shrunk by moving the start of the window to the right until the number of replacements become less than `K`.
41+
5. Throughout this process, the algorithm keeps track of the maximum length of a valid substring (one where the number of replacements is within the allowed limit).
42+
43+
Complexity analysis:
44+
45+
- Time complexity: O(N)
46+
- Space complexity: O(1)
47+
48+
Why time complexity is O(N) and space complexity is O(1)?
49+
50+
The algorithm processes each character in the input string at most twice: once when expanding the window and once when shrinking it.
51+
52+
1. The outer loop that expands the window runs in O(N) time, where `N` is the number of characters in the input string.
53+
2. The inner loop uses the counter hashmap to finds the maximum frequency in each iteration. Since the input string contains only lowercase letters, the maximum number of unique characters is `26`. Hence, this operation is effectively O(26), which is a constant time operation.
54+
55+
Combining these, the overall complexity is O((26 * N) + N), which simplifies to O(N) when dropping the constant factor.
56+
57+
Similarly, the space complexity is O(1) because the maximum number of unique characters in the counter hashmap is 26, which is a constant.
58+
59+
```python
60+
from collections import Counter
61+
62+
def characterReplacement1(s: str, k: int) -> int:
63+
longest_length = 0
64+
65+
window_start = 0
66+
window_counter = Counter()
67+
for window_end in range(len(s)):
68+
window_end_letter = s[window_end]
69+
window_counter[window_end_letter] += 1
70+
71+
# number of replacements = (window size) - (frequency of the most frequent character)
72+
# if replacements exceed k, shrink the window by moving the start
73+
while (window_end - window_start + 1) - max(window_counter.values()) > k:
74+
window_start_letter = s[window_start]
75+
window_counter[window_start_letter] -= 1
76+
window_start += 1
77+
78+
longest_length = max(longest_length, (window_end - window_start) + 1)
79+
80+
return longest_length
81+
```
82+
83+
## Solution 2
84+
85+
The improved algorithm eliminates the need to repeatedly search for the most frequent character in the window during each iteration in the shrinking inner loop. Instead, it tracks the maximum frequency of any character (`max_letter_count`) as the window expands, avoiding recalculation when the window shrinks.
86+
87+
The idea is that `max_letter_count` might become an overestimation when the window shrinks (e.g., if the most frequent character is removed from the window). However, this overestimation doesn’t affect the correctness of the algorithm. As long as the window remains valid, the overestimated `max_letter_count` still ensures the algorithm correctly identifies the longest possible substring that satisfies the replacement condition.
88+
89+
In other words, the `max_letter_count` overestimation does not cause the algorithm to miss a longer valid window. It just means the algorithm might temporarily shrink a window unnecessarily.
90+
91+
For example:
92+
93+
1. If the current window size is `4` and `max_letter_count` is `3`, the window is valid as long as `(window size - max_letter_count) ≤ k`.
94+
2. When the window shrinks, the actual maximum frequency might decrease, but the algorithm only cares about expanding the window to find longer valid substrings.
95+
3. If a longer window is found, a higher maximum frequency (`max_letter_count`) will be needed to maintain the validity of the window under the replacement limit e.g. for longer window size `5`, the `max_letter_count` must be equal to `4` to maintain the same replacement limit `K`.
96+
97+
Complexity analysis:
98+
99+
- Time complexity: O(N)
100+
- Space complexity: O(1)
101+
102+
```python
103+
from collections import Counter
104+
105+
def characterReplacement(s: str, k: int) -> int:
106+
longest_length = 0
107+
108+
window_start = 0
109+
window_counter = Counter()
110+
max_letter_count = 0
111+
for window_end in range(len(s)):
112+
window_end_letter = s[window_end]
113+
window_counter[window_end_letter] += 1
114+
115+
max_letter_count = max(max_letter_count, window_counter[window_end_letter])
116+
117+
# if the number of replacements needed exceeds k, shrink the window
118+
while (window_end - window_start + 1) - max_letter_count > k:
119+
window_start_letter = s[window_start]
120+
window_counter[window_start_letter] -= 1
121+
window_start += 1
122+
123+
longest_length = max(longest_length, (window_end - window_start) + 1)
124+
125+
return longest_length
126+
```

03-sliding-window/README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ This technique is often used for solving problems that require processing or ana
66

77
## Problems
88

9-
| Problem | Complexity |
10-
| :--------------------------------------------------------------------------------------------------: | :---------------------: |
11-
| [Maximum Sum Subarray of Size K](./01-maximum-sum-subarray-of-size-k.md) | :star2: |
12-
| [Smallest Subarray with a Given Sum](./02-smallest-subarray-with-a-given-sum.md) | :star2: |
13-
| [Longest Substring with K Distinct Characters](./03-longest-substring-with-k-distinct-characters.md) | :star2: :star2: |
14-
| [Fruits into Baskets](./04-fruits-into-baskets.md) | :star2: :star2: |
15-
| [No-repeat Substring](./05-no-repeat-substring.md) | :star2: :star2: :star2: |
9+
| Problem | Complexity |
10+
| :--------------------------------------------------------------------------------------------------------------------: | :---------------------: |
11+
| [Maximum Sum Subarray of Size K](./01-maximum-sum-subarray-of-size-k.md) | :star2: |
12+
| [Smallest Subarray with a Given Sum](./02-smallest-subarray-with-a-given-sum.md) | :star2: |
13+
| [Longest Substring with K Distinct Characters](./03-longest-substring-with-k-distinct-characters.md) | :star2: :star2: |
14+
| [Fruits into Baskets](./04-fruits-into-baskets.md) | :star2: :star2: |
15+
| [No-repeat Substring](./05-no-repeat-substring.md) | :star2: :star2: :star2: |
16+
| [Longest Substring with Same Letters after Replacement](./06-longest-substring-with-same-letters-after-replacement.md) | :star2: :star2: :star2: |

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ This repository contains my solutions and notes for the Educative's [Grokking Co
3333

3434
### Sliding Window
3535

36-
| Problem |
37-
| :--------------------------------------------------------------------------------------------------------------------: |
38-
| [Maximum Sum Subarray of Size K](./03-sliding-window/01-maximum-sum-subarray-of-size-k.md) |
39-
| [Smallest Subarray with a Given Sum](./03-sliding-window/02-smallest-subarray-with-a-given-sum.md) |
40-
| [Longest Substring with K Distinct Characters](./03-sliding-window/03-longest-substring-with-k-distinct-characters.md) |
41-
| [Fruits into Baskets](./03-sliding-window/04-fruits-into-baskets.md) |
42-
| [No-repeat Substring](./03-sliding-window/05-no-repeat-substring.md) |
36+
| Problem |
37+
| :--------------------------------------------------------------------------------------------------------------------------------------: |
38+
| [Maximum Sum Subarray of Size K](./03-sliding-window/01-maximum-sum-subarray-of-size-k.md) |
39+
| [Smallest Subarray with a Given Sum](./03-sliding-window/02-smallest-subarray-with-a-given-sum.md) |
40+
| [Longest Substring with K Distinct Characters](./03-sliding-window/03-longest-substring-with-k-distinct-characters.md) |
41+
| [Fruits into Baskets](./03-sliding-window/04-fruits-into-baskets.md) |
42+
| [No-repeat Substring](./03-sliding-window/05-no-repeat-substring.md) |
43+
| [Longest Substring with Same Letters after Replacement](./03-sliding-window/06-longest-substring-with-same-letters-after-replacement.md) |
4344

4445
## Courses
4546

0 commit comments

Comments
 (0)