3399. Smallest Substring With Identical Characters II
Problem Description
You are given a binary string s
of length n
and an integer numOps
. You are allowed to perform the following operation on s
at most numOps
times:
- Select any index
i
(where0 <= i < n
) and flips[i]
. Ifs[i] == '1'
, changes[i]
to'0'
and vice versa.
You need to minimize the length of the longest substring of s
such that all the characters in the substring are identical.
Return the minimum length after the operations.
Intuition
To solve this problem, the goal is to minimize the length of the longest contiguous substring of identical characters after performing at most numOps
flips. The approach involves using binary search over possible lengths of the longest identical substring and a function to check if it's feasible to achieve a given length with the allowed number of flips.
-
Binary Search Approach: We utilize binary search to efficiently determine the smallest possible length of the longest substring where all characters are identical.
-
Feasibility Check: For a given potential length, use a helper function to check whether it's possible to break all blocks of identical characters that exceed this length by performing the available flips. For substrings longer than this length, calculate how many operations are required to divide them into smaller acceptable lengths.
-
Counting Flips: For the specific case where we check for a substring of length 1, we alternate the characters to minimize contiguous sequences, ensuring we're within the flip limit. For longer substrings, track contiguous sequences and calculate the necessary flips to fragment these blocks.
By structuring the solution in this way, the algorithm efficiently finds the optimal length of the longest permissible identical substring.
Learn more about Binary Search patterns.
Solution Approach
The solution employs a combination of binary search and a validation function to find the minimal possible length of the longest contiguous substring of identical characters after allowed flips.
-
- We perform a binary search over possible lengths of the longest identical substring, ranging from
1
ton
(the length of the input strings
). - Use
bisect_left
frombisect
module to implement the binary search efficiently.
- We perform a binary search over possible lengths of the longest identical substring, ranging from
-
Validation Function (
check
):- Define a helper function
check(m)
to determine if it's feasible to limit the longest identical substring to a lengthm
using at mostnumOps
flips.
- Define a helper function
-
Counting Operations:
- For
m = 1
, alternate the characters ins
(like'010101...'
or'101010...'
) to see if the number of flips needed is withinnumOps
. - For
m > 1
, traverse the string to count the length of contiguous identical substrings. If a block length exceedsm
, calculate how many flips are required to make each segment have at mostm
identical characters. - Update the
cnt
(operation count) accordingly for each long contiguous block.
- For
-
Checking Feasibility:
- The
check
function evaluates whether the computedcnt
does not exceednumOps
for eachm
. If feasible, the binary search narrows the search range.
- The
This method is efficient because it leverages both binary search to quickly hone in on feasible solutions and a counting strategy to validate the intermediate results with precision.
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 using a small example to understand the approach better. Consider the binary string s = "110100110"
with numOps = 2
.
-
Initialization:
- We need to find the minimum length of the longest substring of identical characters after up to 2 flips.
- Use binary search on the length of the longest possible segment of identical characters from
1
to the length ofs
, which is9
.
-
Binary Search:
-
Step 1: Set
left
to 1 andright
to 9. Start withm = (1 + 9) // 2 = 5
.- Check if we can ensure no block is longer than
5
with 2 flips. - A segment like
110
is already of length 2, which is fine. Segments00
and11
are also smaller than5
. - So, it's feasible, and we continue to see if smaller lengths are achievable by setting
right = 5
.
- Check if we can ensure no block is longer than
-
Step 2: Set
m = (1 + 5) // 2 = 3
.- Check if it's possible to limit blocks to length
3
. - Here, we can split parts of
110100110
with flips:- Flip one
1
to0
in the110
or011
substrings, and a0
to1
in the00
or11
substrings if needed, staying within2
operations.
- Flip one
- It's feasible with
2
flips, so we setright = 3
.
- Check if it's possible to limit blocks to length
-
Step 3: Set
m = (1 + 3) // 2 = 2
.- Check if we can reduce to blocks of length
2
. - For
110100110
, to reduce all segments to length2
, notice:- Substring
110
, one flip changes to10
. - Substring
011
, one flip changes to01
. - This uses exactly
2
flips.
- Substring
- Feasible, so set
right = 2
.
- Check if we can reduce to blocks of length
-
Step 4: Set
m = (1 + 2) // 2 = 1
.- Check if each block can be of length
1
. - Alternating pattern requires too many flips (
5
flips needed), exceedingnumOps = 2
. - Not feasible, so set
left = 2
.
- Check if each block can be of length
-
-
Outcome:
- The binary search ends when
left >= right
. - The smallest feasible maximum length of identical character blocks is
2
.
- The binary search ends when
In this example, we used binary search to efficiently find that the minimized maximal block length of identical characters is 2
after performing at most two character flips.
Solution Implementation
1from bisect import bisect_left
2
3class Solution:
4 def minLength(self, s: str, numOps: int) -> int:
5 # Function to check if it's possible to transform s to a repeated pattern of length m
6 def check(m: int) -> bool:
7 cnt = 0 # Initialize the counter for operations
8 # Case when trying to create a pattern "01" or "10"
9 if m == 1:
10 pattern = "01" # Define alternating pattern
11 cnt = sum(char == pattern[i % 2] for i, char in enumerate(s)) # Count how many characters need change to fit pattern
12 cnt = min(cnt, n - cnt) # Minimize operations by choosing the easier pattern ("01" vs "10")
13 else:
14 consecutive_count = 0
15 # Iterate over the string to count unnecessary consecutive patterns
16 for i, char in enumerate(s):
17 consecutive_count += 1 # Increment the consecutive count
18 # If end of the string or next character is different, decide on how many groups can be formed
19 if i == len(s) - 1 or char != s[i + 1]:
20 cnt += consecutive_count // (m + 1) # Add to operation count based on consecutive length
21 consecutive_count = 0 # Reset consecutive count
22 return cnt <= numOps # Check if the number of operations is within allowed limit
23
24 n = len(s) # Length of the string
25 # Use binary search to find the minimum length of repeated pattern that can be formed
26 return bisect_left(range(n), True, lo=1, key=check)
27
1class Solution {
2 private char[] chars; // Array to hold character data
3 private int maxOperations; // Maximum allowable operations
4
5 public int minLength(String s, int numOps) {
6 this.maxOperations = numOps;
7 this.chars = s.toCharArray();
8 int left = 1, right = s.length(); // Initialize binary search boundaries
9 while (left < right) {
10 int mid = (left + right) >> 1; // Calculate the mid-point for binary search
11 if (check(mid)) {
12 right = mid; // Narrow down the search to the left half
13 } else {
14 left = mid + 1; // Narrow down the search to the right half
15 }
16 }
17 return left; // Return the minimum valid length
18 }
19
20 private boolean check(int m) {
21 int operationsCount = 0; // Count of required operations
22
23 if (m == 1) {
24 // Handle the special case when m = 1
25 char[] pattern = {'0', '1'};
26 for (int i = 0; i < chars.length; ++i) {
27 if (chars[i] == pattern[i & 1]) { // Check for alternating pattern
28 ++operationsCount;
29 }
30 }
31 operationsCount = Math.min(operationsCount, chars.length - operationsCount);
32 } else {
33 int segmentLength = 0;
34 for (int i = 0; i < chars.length; ++i) {
35 ++segmentLength;
36 // Check for the end of the segment or a change in character
37 if (i == chars.length - 1 || chars[i] != chars[i + 1]) {
38 operationsCount += segmentLength / (m + 1); // Calculate operations needed for the segment
39 segmentLength = 0; // Reset segment length
40 }
41 }
42 }
43
44 return operationsCount <= maxOperations; // Evaluate if current m is feasible
45 }
46}
47
1#include <string>
2#include <algorithm>
3using namespace std;
4
5class Solution {
6public:
7 int minLength(string s, int numOps) {
8 int n = s.size(); // Get the size of the string
9 auto canAchieve = [&](int m) {
10 int cnt = 0; // Initialize a counter for operations needed
11
12 // Case when m equals 1
13 if (m == 1) {
14 string expectedPattern = "01"; // Alternating pattern
15 for (int i = 0; i < n; ++i) {
16 // Check if current character matches the alternating pattern
17 if (s[i] == expectedPattern[i % 2]) {
18 ++cnt; // Increment count if matches
19 }
20 }
21 cnt = min(cnt, n - cnt); // Get minimum number of operations required
22 } else {
23 int groupSize = 0; // Variable to track group size
24 for (int i = 0; i < n; ++i) {
25 ++groupSize; // Increment group size
26 // Check if it's the end of a group
27 if (i == n - 1 || s[i] != s[i + 1]) {
28 // Calculate the number of operations needed for this group
29 cnt += groupSize / (m + 1);
30 groupSize = 0; // Reset the group size for the next group
31 }
32 }
33 }
34 return cnt <= numOps; // Check if we can achieve this with the given number of operations
35 };
36
37 int left = 1, right = n;
38 while (left < right) {
39 int mid = (left + right) >> 1; // Find the midpoint
40 // If the current mid can be achieved, search in the left half
41 if (canAchieve(mid)) {
42 right = mid;
43 } else {
44 left = mid + 1; // Else search in the right half
45 }
46 }
47 return left; // Return the minimum length found
48 }
49};
50
1// Function to determine the minimum possible length of string after performing operations
2function minLength(s: string, numOps: number): number {
3 const n = s.length;
4
5 // Inner function to check if a given length `m` is achievable with the allowed number of operations
6 const check = (m: number): boolean => {
7 let cnt = 0; // Initialize count of operations performed
8
9 if (m === 1) {
10 const alterPattern = '01'; // Alternating pattern for comparison
11 for (let i = 0; i < n; ++i) {
12 if (s[i] === alterPattern[i % 2]) {
13 // Count the mismatches with alternating pattern
14 ++cnt;
15 }
16 }
17 // Take the minimum of mismatches or matches to calculate operation count
18 cnt = Math.min(cnt, n - cnt);
19 } else {
20 let seqLength = 0; // Length of the current sequence of identical characters
21 for (let i = 0; i < n; ++i) {
22 ++seqLength;
23 if (i === n - 1 || s[i] !== s[i + 1]) {
24 // Calculate the number of operations needed for this sequence
25 cnt += Math.floor(seqLength / (m + 1));
26 seqLength = 0; // Reset the sequence length for the next group
27 }
28 }
29 }
30 // Returns true if the required number of operations is less than or equal to allowed operations
31 return cnt <= numOps;
32 };
33
34 let [left, right] = [1, n]; // Initialize binary search boundaries
35
36 // Binary search to find the minimum length `m`
37 while (left < right) {
38 const mid = (left + right) >> 1; // Middle point
39
40 if (check(mid)) {
41 right = mid; // Try for a smaller length
42 } else {
43 left = mid + 1; // Increase the minimum possible length
44 }
45 }
46 return left; // Return the minimal length found
47}
48
Time and Space Complexity
The time complexity of the provided code is determined primarily by the check function and the use of bisect_left
from Python's bisect
module.
-
The
check
function involves iterating over the strings
, which has a lengthn
. Within this loop, for each character, there is a constant amount of work. Therefore, the time complexity of each call tocheck
isO(n)
. -
The
bisect_left
function is used with thekey
parameter ascheck
, and it performs a binary search over a range from1
ton
. This involves roughlyO(log n)
calls to thecheck
function.
Combining these, the overall time complexity is O(n log n)
.
The space complexity of the solution is O(1)
since no additional data structures are used that grow with input size; the storage required does not depend on the size of n
.
Thus, the complexities are:
- Time Complexity:
O(n log n)
- Space Complexity:
O(1)
Learn more about how to find time and space complexity quickly.
Which type of traversal does breadth first search do?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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 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!