424. Longest Repeating Character Replacement
Problem Description
In this problem, you're provided with a string s
and an integer k
. You are allowed to perform at most k
operations on the string. In each operation, you may choose any character in the string and change it to any other uppercase English letter. The objective is to find the length of the longest substring (that is a sequence of consecutive characters from the string) that contains the same letter after you have performed zero or more, up to k
, operations.
For example, given the string "AABABBA"
and k = 1
, you are allowed to change at most one character. The best way is to change the middle 'A' to 'B', resulting in the string "AABBBBA". The longest substring with identical letters is "BBBB", which is 4 characters long.
Intuition
The intuition behind the solution involves a common technique called the "sliding window". The core idea is to maintain a window (or a subrange) of the string and keep track of certain attributes within this window. As the window expands or contracts, you adjust these attributes and check to see if you can achieve a new maximum.
The solution keeps track of:
-
The frequency of each letter within the window: This is kept in an array called
counter
. Each index of this array corresponds to a letter of the English alphabet. -
The count of the most frequent letter so far: During each step, the code calculates the current window's most frequent letter. This is stored in the variable
maxCnt
. -
The indices
i
(end) andj
(start) of the window.
The approach is as follows:
-
Expand the window by moving
i
to the right, incrementing the counter of the current character. -
Update the
maxCnt
with the maximum frequency of the current character. -
Check if the current window size is greater than the sum of
maxCnt
(the most frequent character count) andk
. If it is, then that means the current window cannot be made into a substring of all identical letters with at mostk
operations. If this happens, decrease the count of the leftmost character and move the start of the window to the right (incrementj
). -
Repeat this process until you have processed the entire string.
-
Since the window size only increases or remains unchanged over time (because we only move
i
to the right and incrementj
when necessary), the final value ofi - j
wheni
has reached the end of the string will be the size of the largest window we were able to create where at mostk
operations would result in a substring of identical letters.
By the time the window has moved past all characters in s
, you've considered every possible substring and the maximum viable window size is the length of the longest substring satisfying the condition. Hence the answer is i - j
. This is an application of the two-pointer technique.
Learn more about Sliding Window patterns.
Solution Approach
To implement the solution, the approach capitalizes on several important concepts and data structures:
-
Sliding Window Technique: This technique involves maintaining a window of elements and slides it over the data to consider different subsets of the data.
-
Two Pointers:
i
andj
are pointers used to represent the current window in the string, wherei
is the end of the window andj
is the beginning. -
Array for Counting: An array
counter
of size 26 is used to keep count of all uppercase English letters within the current sliding window. Since there are only 26 uppercase English letters, it's efficient regarding both space and time complexity.
Here's a step-by-step of what happens in the code:
-
Initialization: Set up an array
counter
with length 26 to zero for all elements, representing the count of each letter. Initialize two pointersi
andj
to0
, andmaxCnt
to0
which will store the maximum frequency of a single letter within the current window. -
Sliding Window Expansion: Iterate over the string using the pointer
i
to expand the window to the right. For each characters[i]
, increment the count in thecounter
array at the position corresponding to the letter (found byord(s[i]) - ord('A')
whereord
is a function that gets the ASCII value of a character). -
Updating Maximum Letter Count: After updating
counter
for the new character, updatemaxCnt
to reflect the maximum frequency of any single character in the current window. -
Exceeding the Operation Limit: At each iteration, check if the current window size (
i - j + 1
) is greater than allowed (maxCnt + k
). If it is, this means more thank
replacements are required to make all characters in the current window the same. Therefore, you need to shrink the window by incrementingj
, and decreasing the count of the character at the start of the window. -
Continue Until the End: Keep repeating steps 2 to 4 until the end of the string is reached. At this point, since the window only grew or remained the same throughout the process, the difference
i - j
will be the length of the longest substring that can be achieved with at mostk
changes.
Using this approach, as you can see in the Python code, the function characterReplacement
operates on the string efficiently by using a fixed amount of memory (the counter
array) and makes a single pass over the string, thus the time complexity is O(n), where n is the length of the string.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution approach using a small example: s = "ABAA"
and k = 1
.
-
Initialization: We start by initializing our
counter
array of size 26 to zero and the pointersi
andj
are both set to0
.maxCnt
is also initialized to0
. -
Sliding Window Expansion: Begin iterating through the string.
- For the first character, 'A',
counter[A]
(considercounter[0]
since 'A' is the first letter) is incremented to1
. NowmaxCnt
also becomes1
, as the only character in the window is 'A'. - Move
i
to the right to point to the second characters[1]
which is 'B'. Incrementcounter[B]
(considercounter[1]
since 'B' is the second letter).maxCnt
remains1
because the frequency of both 'A' and 'B' is the same (1
) in the window.
- For the first character, 'A',
-
Updating Maximum Letter Count: As we continue, we update
counter
andmaxCnt
:- Increment
i
to point tos[2]
, which is 'A'. Nowcounter[A]
is2
. We then updatemaxCnt
to2
, as 'A' is now the most frequent letter within the window.
- Increment
-
Exceeding the Operation Limit: Continue expanding the window by moving
i
to the right:- Increment
i
to point tos[3]
, which is 'A'.counter[A]
is now incremented to3
. The window size is4
(froms[0]
tos[3]
), and sincemaxCnt
is3
andk
is1
, we satisfy the condition(window size) <= (maxCnt + k)
, which is4 <= (3 + 1)
.
- Increment
-
Shrinking the Window: At this point, since we're at the end of the string, we stop and observe that we did not have to shrink the window at any point. The largest window we could form went from index
0
to index3
with one operation allowed to change a 'B' to an 'A', resulting in all 'A's.
The longest substring that can be formed from string "ABAA"
by changing no more than 1
letter is "AAAA"
, which has a length of 4
. So, our output is 4
, which is the length from pointer j
to i
.
Using this step-by-step walkthrough, it is evident that this approach is both systematic and efficient in determining the length of the longest substring where at most k
changes result in a uniform string.
Solution Implementation
1class Solution:
2 def characterReplacement(self, s: str, k: int) -> int:
3 # Initialize the frequency counter for the 26 letters of the alphabet
4 frequency_counter = [0] * 26
5 # Initialize pointers for the sliding window
6 left = right = 0
7 # Variable to keep track of the count of the most frequent character
8 max_frequency = 0
9
10 # Iterate over the characters in the string
11 while right < len(s):
12 # Update the frequency of the current character
13 frequency_counter[ord(s[right]) - ord('A')] += 1
14 # Find the maximum frequency count so far
15 max_frequency = max(max_frequency, frequency_counter[ord(s[right]) - ord('A')])
16
17 # Calculate the window size and compare it with the maximum frequency count and allowed replacements (k)
18 if (right - left + 1) > max_frequency + k:
19 # If the condition is true, decrement the frequency of the leftmost character
20 # as it will be excluded from the current window
21 frequency_counter[ord(s[left]) - ord('A')] -= 1
22 # Shrink the window by moving the left pointer forward
23 left += 1
24
25 # Move the right pointer forward to expand the window
26 right += 1
27
28 # Return the maximum length of the window
29 return right - left
30
1class Solution {
2 public int characterReplacement(String s, int k) {
3 int[] letterCount = new int[26]; // Array to store the frequency count of each letter
4 int windowStart = 0; // Start index of the sliding window
5 int windowEnd = 0; // End index of the sliding window
6 int maxCountInWindow = 0; // Variable to store the maximum count of a single character in the current window
7
8 // Iterate over the string with windowEnd serving as the end pointer of the sliding window
9 for (; windowEnd < s.length(); ++windowEnd) {
10 char currentChar = s.charAt(windowEnd); // Current character in iteration
11 letterCount[currentChar - 'A']++; // Increment the count for this character in the frequency array
12
13 // Update the maxCountInWindow to be the max between itself and the count of the current character
14 maxCountInWindow = Math.max(maxCountInWindow, letterCount[currentChar - 'A']);
15
16 // Check if current window size minus max frequency count is greater than k
17 // If it is, we need to slide the window ahead while decrementing the count of the char at windowStart
18 if (windowEnd - windowStart + 1 - maxCountInWindow > k) {
19 letterCount[s.charAt(windowStart) - 'A']--; // Decrement count of the start character of the window
20 windowStart++; // Move the window's start index forward
21 }
22 }
23 // The maximum length substring is the size of the window on loop exit
24 return windowEnd - windowStart;
25 }
26}
27
1class Solution {
2public:
3 int characterReplacement(string s, int k) {
4 vector<int> charCount(26, 0); // Counter for each letter's frequency within the sliding window
5 int left = 0; // Left index of the sliding window
6 int right = 0; // Right index of the sliding window
7 int maxCharCount = 0; // Variable to keep track of the count of the most frequent character within the window
8
9 // Iterate over the characters of the string
10 for (right = 0; right < s.size(); ++right) {
11 charCount[s[right] - 'A']++; // Increment the count for the current character
12
13 // Update the max frequency character count seen so far in the current window
14 maxCharCount = max(maxCharCount, charCount[s[right] - 'A']);
15
16 // Check if the current window size minus the count of the max frequency character
17 // is greater than k, if so, shrink the window from the left
18 if (right - left + 1 - maxCharCount > k) {
19 charCount[s[left] - 'A']--; // Decrement the count for the character at the left index as it's going out of the window
20 left++; // Shrink the window from the left
21 }
22 }
23
24 // The length of the largest window compliant with the condition serves as the answer
25 return right - left;
26 }
27};
28
1// Counter for each letter's frequency within the sliding window
2const charCount: number[] = new Array(26).fill(0);
3// Left index of the sliding window
4let left: number = 0;
5// Right index of the sliding window
6let right: number = 0;
7// Variable to keep track of the count of the most frequent character within the window
8let maxCharCount: number = 0;
9
10/**
11 * Method to find the length of the longest substring which can be made
12 * by replacing at most k characters with any letter.
13 *
14 * @param {string} s - The input string to be processed
15 * @param {number} k - The maximum number of characters that can be replaced
16 * @returns {number} The maximum length of the substring
17 */
18function characterReplacement(s: string, k: number): number {
19 // Reset variables for a new call
20 charCount.fill(0);
21 left = 0;
22 right = 0;
23 maxCharCount = 0;
24
25 // Iterate over the characters of the string
26 for (right = 0; right < s.length; ++right) {
27 // Increment the count for the current character
28 charCount[s.charCodeAt(right) - 'A'.charCodeAt(0)]++;
29
30 // Update the max frequency character count seen so far in the current window
31 maxCharCount = Math.max(maxCharCount, charCount[s.charCodeAt(right) - 'A'.charCodeAt(0)]);
32
33 // Check if the current window size minus the count of the max frequency character
34 // is greater than k. If so, shrink the window from the left.
35 if (right - left + 1 - maxCharCount > k) {
36 // Decrement the count for the character that is exiting the window
37 charCount[s.charCodeAt(left) - 'A'.charCodeAt(0)]--;
38 // Move the left pointer to shrink the window
39 left++;
40 }
41 }
42
43 // The length of the largest window compliant with the condition serves as the answer
44 return right - left;
45}
46
Time and Space Complexity
The given code implements a sliding window algorithm to find the longest substring that can be created by replacing at most k
characters in the input string s
.
Time Complexity:
The time complexity of the code is O(n)
, where n
is the length of the input string s
. This is because:
- The algorithm uses two pointers
i
(end of the window) andj
(start of the window) that move through the string only once. - Inside the
while
loop, the algorithm performs a constant number of operations for each character in the string: updating thecounter
array, computingmaxCnt
, comparing window size withmaxCnt + k
, and incrementing or decrementing the pointers and counter. - Although there is a
max
operation inside the loop which comparesmaxCnt
with the count of the current character. This comparison takes constant time becausemaxCnt
is only updated with values coming from a fixed-size array (thecounter
array with 26 elements representing the count of each uppercase letter in the English alphabet). - No nested loops are dependent on the size of
s
, so the complexity is linear with the length ofs
.
Space Complexity:
The space complexity of the code is O(1)
:
- The
counter
array uses space for 26 integers, which is a constant size and does not depend on the length of the input strings
. - Only a fixed number of integer variables (
i
,j
,maxCnt
) are used, which also contributes to a constant amount of space.
In conclusion, the algorithm runs in linear time and uses a constant amount of additional space.
Learn more about how to find time and space complexity quickly using problem constraints.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!