3398. Smallest Substring With Identical Characters I
Problem Description
You have a binary string s consisting of only '0's and '1's, with length n. You're allowed to perform up to numOps operations on this string.
Each operation allows you to:
- Choose any index
i(where0 <= i < n) - Flip the character at that position (change '0' to '1' or '1' to '0')
Your goal is to minimize the length of the longest substring where all characters are identical (all '0's or all '1's).
For example:
- In string "111001", the longest substring with identical characters is "111" with length 3
- After some flips, you might reduce this maximum length
The task is to find the minimum possible length of such longest identical substring after performing at most numOps operations optimally.
The solution uses binary search to find this minimum length. For each candidate length m, it checks whether it's possible to achieve that maximum length using at most numOps flips:
- When
m = 1, the string should alternate like "010101..." or "101010...", and we count how many flips are needed - For
m > 1, we count how many flips are needed to break up each consecutive group of identical characters into segments of at most lengthm
The binary search finds the smallest m where the required number of flips doesn't exceed numOps.
Intuition
The key insight is that we're looking for the minimum possible value of the maximum consecutive identical characters. This "minimize the maximum" pattern naturally suggests binary search on the answer.
Think about it this way: if we can achieve a maximum length of m, then we can definitely achieve any length greater than m (we just need fewer or the same number of flips). Conversely, if we cannot achieve length m with our available operations, we cannot achieve any length smaller than m either. This monotonic property makes binary search applicable.
For any target maximum length m, we need to figure out the minimum number of flips required to ensure no consecutive identical substring exceeds length m:
-
Special case when
m = 1: This means we want no two adjacent characters to be the same - the string must alternate perfectly like "010101..." or "101010...". We can calculate flips needed for both patterns and take the minimum. -
General case when
m > 1: For each group of consecutive identical characters of lengthk:- If
k <= m, no flips needed for this group - If
k > m, we need to break it up by flipping some characters within it - To break a group of length
kinto segments of at mostm, we needk // (m + 1)flips strategically placed
- If
The formula k // (m + 1) comes from the fact that each flip creates a breakpoint, and to divide a segment of length k into parts of at most length m, we need to place flips at intervals. For example, to break "11111" (length 5) into segments of at most 2, we need 5 // 3 = 1 flip in the middle.
By binary searching on the possible values of m from 1 to n, we find the smallest m where the required flips don't exceed numOps.
Learn more about Binary Search patterns.
Solution Implementation
1class Solution:
2 def minLength(self, s: str, numOps: int) -> int:
3 n = len(s)
4
5 def feasible(m: int) -> bool:
6 """Check if max consecutive length of m is achievable."""
7 ops_needed = 0
8
9 if m == 1:
10 # Need alternating pattern
11 pattern = "01"
12 ops_needed = sum(c == pattern[i & 1] for i, c in enumerate(s))
13 ops_needed = min(ops_needed, n - ops_needed)
14 else:
15 # Count ops to break long consecutive groups
16 k = 0
17 for i, c in enumerate(s):
18 k += 1
19 if i == n - 1 or c != s[i + 1]:
20 ops_needed += k // (m + 1)
21 k = 0
22
23 return ops_needed <= numOps
24
25 # Binary search template: find first m where feasible(m) is True
26 left, right = 1, n
27 first_true_index = -1
28
29 while left <= right:
30 mid = (left + right) // 2
31 if feasible(mid):
32 first_true_index = mid
33 right = mid - 1 # Look for smaller m
34 else:
35 left = mid + 1
36
37 return first_true_index
381class Solution {
2 private char[] chars;
3 private int numOps;
4 private int n;
5
6 public int minLength(String s, int numOps) {
7 this.chars = s.toCharArray();
8 this.numOps = numOps;
9 this.n = s.length();
10
11 // Binary search template: find first m where feasible(m) is True
12 int left = 1;
13 int right = n;
14 int firstTrueIndex = -1;
15
16 while (left <= right) {
17 int mid = left + (right - left) / 2;
18 if (feasible(mid)) {
19 firstTrueIndex = mid;
20 right = mid - 1; // Look for smaller m
21 } else {
22 left = mid + 1;
23 }
24 }
25
26 return firstTrueIndex;
27 }
28
29 private boolean feasible(int m) {
30 int opsNeeded = 0;
31
32 if (m == 1) {
33 char[] pattern = {'0', '1'};
34 for (int i = 0; i < n; i++) {
35 if (chars[i] == pattern[i & 1]) {
36 opsNeeded++;
37 }
38 }
39 opsNeeded = Math.min(opsNeeded, n - opsNeeded);
40 } else {
41 int k = 0;
42 for (int i = 0; i < n; i++) {
43 k++;
44 if (i == n - 1 || chars[i] != chars[i + 1]) {
45 opsNeeded += k / (m + 1);
46 k = 0;
47 }
48 }
49 }
50
51 return opsNeeded <= numOps;
52 }
53}
541class Solution {
2public:
3 int minLength(string s, int numOps) {
4 int n = s.size();
5
6 auto feasible = [&](int m) -> bool {
7 int opsNeeded = 0;
8
9 if (m == 1) {
10 string pattern = "01";
11 for (int i = 0; i < n; ++i) {
12 if (s[i] == pattern[i & 1]) {
13 ++opsNeeded;
14 }
15 }
16 opsNeeded = min(opsNeeded, n - opsNeeded);
17 } else {
18 int k = 0;
19 for (int i = 0; i < n; ++i) {
20 ++k;
21 if (i == n - 1 || s[i] != s[i + 1]) {
22 opsNeeded += k / (m + 1);
23 k = 0;
24 }
25 }
26 }
27
28 return opsNeeded <= numOps;
29 };
30
31 // Binary search template: find first m where feasible(m) is True
32 int left = 1;
33 int right = n;
34 int firstTrueIndex = -1;
35
36 while (left <= right) {
37 int mid = left + (right - left) / 2;
38 if (feasible(mid)) {
39 firstTrueIndex = mid;
40 right = mid - 1; // Look for smaller m
41 } else {
42 left = mid + 1;
43 }
44 }
45
46 return firstTrueIndex;
47 }
48};
491function minLength(s: string, numOps: number): number {
2 const n: number = s.length;
3
4 const feasible = (m: number): boolean => {
5 let opsNeeded: number = 0;
6
7 if (m === 1) {
8 const pattern: string = '01';
9 for (let i = 0; i < n; i++) {
10 if (s[i] === pattern[i & 1]) {
11 opsNeeded++;
12 }
13 }
14 opsNeeded = Math.min(opsNeeded, n - opsNeeded);
15 } else {
16 let k: number = 0;
17 for (let i = 0; i < n; i++) {
18 k++;
19 if (i === n - 1 || s[i] !== s[i + 1]) {
20 opsNeeded += Math.floor(k / (m + 1));
21 k = 0;
22 }
23 }
24 }
25
26 return opsNeeded <= numOps;
27 };
28
29 // Binary search template: find first m where feasible(m) is True
30 let left: number = 1;
31 let right: number = n;
32 let firstTrueIndex: number = -1;
33
34 while (left <= right) {
35 const mid: number = Math.floor((left + right) / 2);
36 if (feasible(mid)) {
37 firstTrueIndex = mid;
38 right = mid - 1; // Look for smaller m
39 } else {
40 left = mid + 1;
41 }
42 }
43
44 return firstTrueIndex;
45}
46Solution Approach
The solution uses the boundary-finding binary search template to find the minimum possible maximum length of consecutive identical characters.
Binary Search Template Structure:
We search for the minimum m where feasible(m) returns True. The search space is [1, n].
left, right = 1, n first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 # Look for smaller m else: left = mid + 1 return first_true_index
The Feasible Function:
feasible(m) determines if we can achieve a maximum consecutive length of m using at most numOps flips.
Case 1: When m = 1
- We need the string to alternate completely
- Count operations for pattern "010101..." using
sum(c == pattern[i & 1]) - Take minimum of this count and
n - count(for pattern "101010...")
Case 2: When m > 1
- For each group of
kconsecutive identical characters:- Calculate flips needed:
k // (m + 1) - This formula optimally breaks a segment into parts of at most
m
- Calculate flips needed:
Why This Template Works:
The feasible function creates a monotonic pattern: False, False, ..., True, True, True. If we can achieve max length m, we can certainly achieve any length greater than m. We find the first True (minimum achievable m).
Time Complexity: O(n log n) where binary search runs O(log n) iterations, each check takes O(n).
Space Complexity: O(1) - only using a few variables.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example:
- String
s = "111001" numOps = 2(we can perform up to 2 flips)
Initial Analysis:
- Current consecutive groups: "111" (length 3), "00" (length 2), "1" (length 1)
- Maximum consecutive length = 3
Binary Search Process:
We'll binary search on the answer range [1, 6] (string length is 6).
Iteration 1: Check if m = 3 is achievable
- For each group:
- "111" (length 3): needs
3 // (3+1) = 0flips - "00" (length 2): needs
2 // (3+1) = 0flips - "1" (length 1): needs
1 // (3+1) = 0flips
- "111" (length 3): needs
- Total flips needed: 0 ≤ 2 ✓
- Since we can achieve m=3, try smaller values
Iteration 2: Check if m = 2 is achievable
- For each group:
- "111" (length 3): needs
3 // (2+1) = 1flip - "00" (length 2): needs
2 // (2+1) = 0flips - "1" (length 1): needs
1 // (2+1) = 0flips
- "111" (length 3): needs
- Total flips needed: 1 ≤ 2 ✓
- Since we can achieve m=2, try smaller values
Iteration 3: Check if m = 1 is achievable
- This requires alternating pattern
- Pattern "010101": compare with "111001"
- Positions needing flips: indices 0, 1, 3 → 3 flips
- Pattern "101010": compare with "111001"
- Positions needing flips: indices 2, 4, 5 → 3 flips
- Minimum flips needed: 3 > 2 ✗
- Cannot achieve m=1
Result: The minimum achievable maximum consecutive length is 2.
To verify: With 1 flip at index 1, we get "101001" with maximum consecutive length of 2 ("00").
Time and Space Complexity
Time Complexity: O(n * log n)
The algorithm uses binary search on the range [1, n] where n is the length of string s. The binary search performs O(log n) iterations.
For each iteration of binary search, the check function is called:
- When
m == 1: The function iterates through the string once to count mismatches with alternating patterns, takingO(n)time. - When
m > 1: The function iterates through the string once to count consecutive segments and calculate required operations, takingO(n)time.
Therefore, the overall time complexity is O(n * log n).
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- The
checkfunction uses variablescnt,k, andt(whenm == 1) - The string
t = "01"whenm == 1is constant size - The binary search itself only requires a few variables for bounds and mid-point calculations
The space used does not scale with the input size, resulting in O(1) space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
A critical mistake is using the while left < right with right = mid pattern instead of the correct template.
Incorrect approach:
left, right = 1, n while left < right: mid = (left + right) // 2 if feasible(mid): right = mid else: left = mid + 1 return left
Correct approach (template-compliant):
left, right = 1, n first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
The template uses while left <= right with right = mid - 1 and tracks the answer in first_true_index.
2. Incorrect Formula for Breaking Consecutive Groups
Using the wrong formula to calculate flips needed:
k // m- doesn't account for spacing correctly(k - 1) // m- underestimates
Correct formula: k // (m + 1) - places flips every (m + 1) positions optimally.
3. Misunderstanding the Alternating Pattern Check
When checking for m = 1, forgetting to consider both patterns ("010101..." and "101010..."):
# Wrong: Only checking one pattern
cnt = sum(c == pattern[i & 1] for i, c in enumerate(s))
# Correct: Take minimum of both patterns
cnt = min(cnt, n - cnt)
4. Not Resetting the Consecutive Counter
Forgetting to reset k = 0 after processing each group leads to incorrect calculations for subsequent groups.
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
141public 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}
271function 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}
27Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview 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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way 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 assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!