3666. Minimum Operations to Equalize Binary String
Problem Description
You are given a binary string s (a string consisting only of the characters '0' and '1'), and an integer k.
In a single operation, you must perform the following:
- Choose exactly
kdifferent indices from the string. - For each chosen index, flip the character: every
'0'becomes'1', and every'1'becomes'0'.
It is important to note that you must select exactly k indices in every operation—no more and no fewer. The chosen indices must be distinct within a single operation.
Your goal is to make all characters in the string equal to '1'.
Return the minimum number of operations required to achieve this. If it is impossible to make every character '1', return -1.
Example walk-through of an operation:
Suppose s = "010" and k = 2. You must pick exactly 2 indices to flip. For instance:
- Choosing indices
0and1transforms"010"into"100"(index0:'0'→'1', index1:'1'→'0'). - Choosing indices
0and2transforms"010"into"111"(index0:'0'→'1', index2:'0'→'1').
In this case, a single operation choosing indices 0 and 2 makes all characters '1', so the answer would be 1.
The challenge lies in deciding, at each step, which k indices to flip so that you reach the all-'1' string in as few operations as possible.
How We Pick the Algorithm
Why BFS?
This problem maps to BFS through a short path in the full flowchart.
The problem finds the minimum operations by treating each possible k-flip as a state transition, using BFS (or direct parity reasoning) to reach the all-1s target.
Open in FlowchartShow step-by-step reasoning
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: Although the input is a binary string, we can model each distinct count of
'0's as a node, and each operation that transforms one count into another as an edge. This turns the problem into a state-transition graph where we search for the shortest path from the initial state to the target state.
Is it a tree?
- No: The state-transition graph is not a tree. Multiple different states can transition into the same state, and cycles between states are possible, so it does not have a tree structure.
Is the problem related to directed acyclic graphs?
- No: The graph is not a DAG. Operations can move the count of
'0's up and down, allowing transitions in both directions, so the graph may contain cycles.
Is the problem related to shortest paths?
- Yes: We want the minimum number of operations to reach the all-
'1'state. Each operation is one step, so this is precisely a shortest-path question on the state graph.
Is the graph Weighted?
- No: Every operation costs exactly one step. All edges in the state-transition graph carry the same uniform weight, so the graph is unweighted.
Conclusion: Since we are finding the shortest path in an unweighted state-transition graph, the flowchart guides us to use the BFS (Breadth-First Search) pattern. We BFS over states defined by the number of '0's, starting from the initial count and expanding to all reachable counts in the valid range [l, r] each level, until we reach state 0.
Intuition
The first key observation is that the specific positions of the '0's and '1's don't actually matter—only how many '0's remain in the string. Why? Because in each operation we are free to choose any k indices we like. So the only thing that defines a "state" is the current count of '0's, which we call cur. Our starting state is the number of '0's in s, and our goal is to reach the state where cur == 0.
Next, let's think about what a single operation does to cur. We pick exactly k indices to flip. Suppose x of those chosen indices are '0's (they become '1's, reducing the count of '0's), and the remaining k - x are '1's (they become '0's, increasing the count). After the flip, the new number of '0's is:
cur + (k - x) - x = cur + k - 2x
So from state cur, one operation can land us on any value of the form cur + k - 2x, depending on how many '0's we choose to flip.
Now we figure out the valid range of x:
- We can't flip more
'0's than exist, so0 <= x <= min(cur, k). - We can't flip more
'1's than exist either. The number of'1's flipped isk - x, and there aren - curones available, sok - x <= n - cur, which givesx >= k - n + cur.
Combining, x ranges over [max(k - n + cur, 0), min(cur, k)]. Plugging the extremes of x into cur + k - 2x, the reachable new counts form a contiguous range [l, r] where:
l = cur + k - 2 * min(cur, k)r = cur + k - 2 * max(k - n + cur, 0)
A neat detail: since each step changes cur by k - 2x, the parity of cur only changes based on the parity of k. More precisely, cur + k - 2x always has the same parity as cur + k, so consecutive reachable states skip by 2 and share a fixed parity. This means within the range [l, r], only the values matching the correct parity are actually reachable in one step.
Putting it all together, this becomes a shortest-path search on a graph where each node is a possible count of '0's, and edges connect cur to every reachable count in [l, r]. Since every operation counts as one step (unweighted edges), BFS finds the minimum number of operations. The BFS level acts as our operation counter, and the moment we dequeue state 0, we return that level.
The final piece is efficiency. The range [l, r] could be huge, and naively iterating over all values inside it on every step would be far too slow. To keep things fast, we maintain two ordered sets—one holding the still-unvisited even counts and one holding the unvisited odd counts. When processing a state, we only need to look at the set matching the required parity, jump to the first value >= l, and sweep forward while values stay <= r. Each count is added to a set once and removed once, so we never process the same state twice, keeping the whole search efficient.
Pattern Learn more about Breadth-First Search, Union Find and Math patterns.
Solution Approach
We solve this with BFS over an ordered set of states. Each state is the current number of '0's in the string, and we search for the shortest path (fewest operations) from the initial count to state 0.
Step 1: Set up the ordered sets by parity.
We let n be the length of s. As established in the intuition, every operation changes the count of '0's by k - 2x, so reachable states always preserve a fixed parity relationship. To exploit this, we keep two SortedSets, one for even counts and one for odd counts:
ts = [SortedSet() for _ in range(2)]
for i in range(n + 1):
ts[i % 2].add(i)
This pre-loads every possible count of '0's from 0 to n into its corresponding parity bucket. ts[0] holds all even counts, and ts[1] holds all odd counts. These sets represent the unvisited states.
Step 2: Initialize the BFS from the starting state.
We compute the initial number of '0's, mark it as visited by removing it from its ordered set, and push it into the BFS queue:
cnt0 = s.count('0') ts[cnt0 % 2].remove(cnt0) q = deque([cnt0]) ans = 0
Here ans counts the number of operations, which equals the BFS depth.
Step 3: Process the BFS level by level.
We run a standard level-order BFS. The for _ in range(len(q)) loop processes all states at the current depth before incrementing ans, so ans correctly tracks the number of operations:
while q:
for _ in range(len(q)):
cur = q.popleft()
if cur == 0:
return ans
...
ans += 1
The moment we dequeue state 0, we have reached the all-'1' string, so we return ans immediately.
Step 4: Compute the reachable range and expand.
For each dequeued state cur, we calculate the contiguous range [l, r] of new counts reachable in one operation, using the formulas derived earlier:
l = cur + k - 2 * min(cur, k)
r = cur + k - 2 * max(k - n + cur, 0)
lcorresponds to flipping as many'0's as possible (x = min(cur, k)).rcorresponds to flipping as few'0's as possible (x = max(k - n + cur, 0)).
All reachable states share the parity of l, so we only look in the matching set t = ts[l % 2]. We use bisect_left to jump straight to the first unvisited value >= l, then sweep forward while values stay <= r:
t = ts[l % 2]
j = t.bisect_left(l)
while j < len(t) and t[j] <= r:
q.append(t[j])
t.remove(t[j])
Each reachable, still-unvisited state in [l, r] is added to the queue and removed from the ordered set so it is never revisited. Because we remove from the front of the set after each bisect_left, the index j continues to point at the next candidate.
Step 5: Handle the impossible case.
If the BFS drains the queue without ever reaching state 0, no sequence of operations can make the string all '1', so we return -1:
return -1
Why this is efficient.
The naive approach of iterating over every integer in [l, r] on each step could be O(n) per state, leading to O(n^2) total work. By storing unvisited states in ordered sets and removing each one the first time it is reached, every state is inserted once and removed once across the entire search. The bisect_left lets us skip directly to the relevant portion of the range. This keeps the overall complexity to roughly O(n log n), where n is the length of the string, since each of the n + 1 possible states is processed at most once with logarithmic-time ordered-set operations.
Example Walkthrough
Let's trace the solution approach with a small example.
Input: s = "0011", k = 3
Setup:
n = 4(length of the string).- Number of
'0's ins:cnt0 = 2. This is our starting state. - Goal: reach state
0(zero'0's remaining).
Step 1: Build parity-based ordered sets.
Possible counts of '0's range from 0 to 4. We split them by parity:
ts[0](even) ={0, 2, 4}ts[1](odd) ={1, 3}
These represent all unvisited states.
Step 2: Initialize BFS.
- Remove the start state
2from its set:ts[0]becomes{0, 4}. - Queue:
q = [2] ans = 0
Current sets:
ts[0] = {0, 4}ts[1] = {1, 3}
Step 3 & 4: BFS Level 0 (ans = 0).
Process cur = 2. It is not 0, so we compute the reachable range.
l = cur + k - 2 * min(cur, k) = 2 + 3 - 2 * min(2, 3) = 2 + 3 - 4 = 1r = cur + k - 2 * max(k - n + cur, 0) = 2 + 3 - 2 * max(3 - 4 + 2, 0) = 5 - 2 * max(1, 0) = 5 - 2 = 3
So from state 2, one operation reaches counts in [1, 3] sharing parity with l = 1 (odd). We look only in ts[1] = {1, 3}.
Sweep from the first value >= 1:
1is<= 3→ add to queue, remove from set.3is<= 3→ add to queue, remove from set.
Now:
ts[1] = {}(empty)q = [1, 3]
After finishing this level, ans becomes 1.
Verification of meaning: From state 2 (two '0's), flipping k = 3 indices, we can choose x zeros to flip where x ∈ [1, 2]:
x = 2: flip both'0's and one'1'→ new count= 2 + 3 - 4 = 1.x = 1: flip one'0'and two'1's → new count= 2 + 3 - 2 = 3.
This matches the reachable range {1, 3}.
BFS Level 1 (ans = 1).
Process the two states 1 and 3.
Process cur = 1: Not 0. Compute range:
l = 1 + 3 - 2 * min(1, 3) = 4 - 2 = 2r = 1 + 3 - 2 * max(3 - 4 + 1, 0) = 4 - 2 * max(0, 0) = 4
Range [2, 4], parity of l = 2 is even → look in ts[0] = {0, 4}.
Sweep from first value >= 2:
4is<= 4→ add to queue, remove from set.- (
0is below2, so skipped.)
Now:
ts[0] = {0}q = [3, 4]
Process cur = 3: Not 0. Compute range:
l = 3 + 3 - 2 * min(3, 3) = 6 - 6 = 0r = 3 + 3 - 2 * max(3 - 4 + 3, 0) = 6 - 2 * max(2, 0) = 6 - 4 = 2
Range [0, 2], parity of l = 0 is even → look in ts[0] = {0}.
Sweep from first value >= 0:
0is<= 2→ add to queue, remove from set.
Now:
ts[0] = {}(empty)q = [4, 0]
After finishing this level, ans becomes 2.
BFS Level 2 (ans = 2).
Process states 4 and 0.
Process cur = 4: Not 0. (Its expansion would continue, but it's irrelevant now.)
Process cur = 0: This is the goal state! We return ans = 2.
Result: 2 operations.
Sanity check of the path: 2 → 3 → 0
- Start:
"0011"(2 zeros). Flip indices0,1,2:"1101"(1 zero)...
Let me use the path 2 → 1 → 0 instead for a cleaner illustration:
- Start
"0011"(2 zeros). Flip one'0'and two'1's, e.g. indices0, 2, 3:"1100". That gives 2 zeros, not 1.
The cleanest concrete path matching 2 → 3 → 0:
- Op 1:
"0011", flip indices0, 1, 2(two'0's... only flip one zero). Flip indices0, 2, 3: index0'0'→'1', index2'1'→'0', index3'1'→'0'→"1000"(3 zeros). ✓ State3. - Op 2:
"1000", flip indices1, 2, 3: all'0'→'1'→"1111"(0 zeros). ✓ State0.
Two operations achieve the all-'1' string, confirming the answer of 2.
Solution Implementation
1from collections import deque
2from sortedcontainers import SortedSet
3
4
5class Solution:
6 def minOperations(self, s: str, k: int) -> int:
7 # Total length of the binary string
8 length = len(s)
9
10 # Maintain two SortedSets, partitioned by parity (even / odd).
11 # available_by_parity[p] holds all reachable counts whose value % 2 == p.
12 # These represent the possible "number of 1s" states we can still visit.
13 available_by_parity = [SortedSet() for _ in range(2)]
14 for value in range(length + 1):
15 available_by_parity[value % 2].add(value)
16
17 # Initial state: the number of zeros in the string.
18 # (The BFS works on the count of zeros, aiming to reduce it to 0.)
19 zero_count = s.count('0')
20
21 # Remove the starting state from the available pool so it isn't revisited.
22 available_by_parity[zero_count % 2].remove(zero_count)
23
24 # BFS queue holds the current frontier of zero-count states.
25 queue = deque([zero_count])
26
27 # Number of operations performed so far (BFS level).
28 operations = 0
29
30 while queue:
31 # Process every state in the current BFS level.
32 for _ in range(len(queue)):
33 current = queue.popleft()
34
35 # Reaching zero zeros means the string is fully transformed.
36 if current == 0:
37 return operations
38
39 # Compute the reachable range of next states after one operation.
40 # Each operation flips exactly k characters; the new zero count
41 # depends on how many of those flips hit zeros versus ones.
42 #
43 # left = current + k - 2 * (max zeros we can flip)
44 # right = current + k - 2 * (min zeros we must flip)
45 left = current + k - 2 * min(current, k)
46 right = current + k - 2 * max(k - length + current, 0)
47
48 # The parity of all reachable next states is fixed by `left`.
49 target_set = available_by_parity[left % 2]
50
51 # Collect every still-available state within [left, right].
52 index = target_set.bisect_left(left)
53 while index < len(target_set) and target_set[index] <= right:
54 next_state = target_set[index]
55 queue.append(next_state)
56 # Mark as visited by removing it from the available pool.
57 target_set.remove(next_state)
58 # After removal, the element at `index` shifts, so we don't
59 # advance the index manually—re-check the same position.
60
61 # Completed one BFS level; one more operation has been applied.
62 operations += 1
63
64 # No sequence of operations reaches a zero count of 0.
65 return -1
661class Solution {
2 public int minOperations(String s, int k) {
3 int n = s.length();
4
5 // Two TreeSets partitioned by parity (even/odd indices).
6 // They store all the "unvisited" zero-count states from 0 to n.
7 // Splitting by parity lets us query a parity-consistent range quickly.
8 @SuppressWarnings("unchecked")
9 TreeSet<Integer>[] unvisited = new TreeSet[2];
10 Arrays.setAll(unvisited, i -> new TreeSet<>());
11
12 // Initialize every possible zero-count value [0, n] as unvisited,
13 // placed into the bucket matching its parity.
14 for (int i = 0; i <= n; i++) {
15 unvisited[i % 2].add(i);
16 }
17
18 // Count the number of '0' characters in the input string (start state).
19 int zeroCount = 0;
20 for (char c : s.toCharArray()) {
21 if (c == '0') {
22 zeroCount++;
23 }
24 }
25
26 // Mark the starting state as visited and enqueue it for BFS.
27 unvisited[zeroCount % 2].remove(zeroCount);
28
29 Deque<Integer> queue = new ArrayDeque<>();
30 queue.offer(zeroCount);
31
32 // BFS level by level; each level represents one operation.
33 int operations = 0;
34 while (!queue.isEmpty()) {
35 for (int size = queue.size(); size > 0; --size) {
36 int current = queue.poll();
37
38 // Reaching zero '0's means the string is all '1's: done.
39 if (current == 0) {
40 return operations;
41 }
42
43 // Compute the reachable range of next zero-count states.
44 // Lower bound: flip as many zeros to ones as possible.
45 int low = current + k - 2 * Math.min(current, k);
46 // Upper bound: constrained by how many ones can be flipped to zeros.
47 int high = current + k - 2 * Math.max(k - n + current, 0);
48
49 // All reachable states in [low, high] share the same parity as 'low',
50 // so we only need to scan the matching parity bucket.
51 TreeSet<Integer> bucket = unvisited[low % 2];
52
53 // Pull every unvisited state within [low, high], visit and enqueue it.
54 Integer next = bucket.ceiling(low);
55 while (next != null && next <= high) {
56 queue.offer(next);
57 bucket.remove(next);
58 next = bucket.ceiling(low);
59 }
60 }
61 operations++;
62 }
63
64 // No sequence of operations reaches the all-ones state.
65 return -1;
66 }
67}
681class Solution {
2public:
3 int minOperations(string s, int k) {
4 int n = s.size();
5
6 // unvisitedStates[parity] holds all reachable counts of '0' that share
7 // the same parity (even/odd). We split by parity because one operation
8 // changes the count of zeros by an even amount (see derivation below),
9 // so the parity of the zero-count is preserved across operations.
10 set<int> unvisitedStates[2];
11 for (int i = 0; i <= n; i++) {
12 unvisitedStates[i % 2].insert(i);
13 }
14
15 // Initial number of '0' characters in the string.
16 int initialZeros = count(s.begin(), s.end(), '0');
17
18 // Remove the starting state since we begin our BFS from it.
19 unvisitedStates[initialZeros % 2].erase(initialZeros);
20
21 // BFS over the possible "number of zeros" states. Each level of the
22 // BFS corresponds to one additional operation applied.
23 queue<int> bfsQueue;
24 bfsQueue.push(initialZeros);
25
26 int operations = 0;
27
28 while (!bfsQueue.empty()) {
29 // Process every state belonging to the current BFS level.
30 for (int levelSize = bfsQueue.size(); levelSize > 0; --levelSize) {
31 int currentZeros = bfsQueue.front();
32 bfsQueue.pop();
33
34 // Goal reached: zero '0' characters remain.
35 if (currentZeros == 0) {
36 return operations;
37 }
38
39 // In a single operation we flip k characters. Suppose among the
40 // k chosen positions there are x '0's flipped to '1' and (k - x)
41 // '1's flipped to '0'. The new zero-count becomes:
42 // newZeros = currentZeros - x + (k - x)
43 // = currentZeros + k - 2 * x
44 //
45 // x ranges over the feasible number of zeros we can pick:
46 // - At most min(currentZeros, k) zeros can be flipped.
47 // - We must also flip enough ones; the number of ones is
48 // (n - currentZeros), so (k - x) <= (n - currentZeros),
49 // which gives x >= k - (n - currentZeros) = k - n + currentZeros.
50 // - x cannot be negative, so the lower bound is max(0, k-n+currentZeros).
51 //
52 // Plugging the extreme values of x into newZeros gives the
53 // inclusive range [low, high] of reachable zero-counts:
54 int low = currentZeros + k - 2 * min(currentZeros, k);
55 int high = currentZeros + k - 2 * max(k - n + currentZeros, 0);
56
57 // All reachable states in [low, high] share parity low % 2.
58 auto& states = unvisitedStates[low % 2];
59
60 // Visit every unvisited state within [low, high].
61 auto it = states.lower_bound(low);
62 while (it != states.end() && *it <= high) {
63 bfsQueue.push(*it);
64 it = states.erase(it); // mark as visited to avoid revisiting
65 }
66 }
67 // One full level processed => one more operation used.
68 operations++;
69 }
70
71 // Unreachable: no sequence of operations reduces zeros to 0.
72 return -1;
73 }
74};
751import { AvlTree } from '@datastructures-js/binary-search-tree';
2
3/**
4 * Computes the minimum number of operations needed to reduce the
5 * zero-count of the string to 0, where each operation transforms the
6 * current zero-count into any value within a derived range while
7 * preserving certain parity constraints.
8 *
9 * @param s - The input binary string.
10 * @param k - The operation parameter controlling how counts can change.
11 * @returns The minimum number of operations, or -1 if unreachable.
12 */
13function minOperations(s: string, k: number): number {
14 // Length of the input string.
15 const length: number = s.length;
16
17 // Two AVL trees partitioned by parity (index 0 -> even, 1 -> odd).
18 // Each tree holds all not-yet-visited zero-counts of that parity.
19 const trees: [AvlTree<number>, AvlTree<number>] = [
20 new AvlTree<number>(),
21 new AvlTree<number>(),
22 ];
23
24 // Populate the trees with all possible counts from 0 to length,
25 // placing each value into the tree matching its parity.
26 for (let i = 0; i <= length; i++) {
27 trees[i % 2].insert(i);
28 }
29
30 // Count the number of '0' characters in the string (the start state).
31 let zeroCount: number = 0;
32 for (const ch of s) {
33 if (ch === '0') {
34 zeroCount++;
35 }
36 }
37
38 // Mark the starting count as visited by removing it from its tree.
39 trees[zeroCount % 2].remove(zeroCount);
40
41 // BFS queue initialized with the starting zero-count.
42 let queue: number[] = [zeroCount];
43
44 // The current BFS depth, equal to the number of operations performed.
45 let operations: number = 0;
46
47 // Perform a breadth-first search over reachable zero-counts.
48 while (queue.length > 0) {
49 // Holds the next BFS level's counts.
50 const nextQueue: number[] = [];
51
52 for (const current of queue) {
53 // Reaching a zero-count of 0 means the goal is achieved.
54 if (current === 0) {
55 return operations;
56 }
57
58 // Lower bound of the reachable count range after one operation.
59 const lower: number = current + k - 2 * Math.min(current, k);
60
61 // Upper bound of the reachable count range after one operation.
62 const upper: number =
63 current + k - 2 * Math.max(k - length + current, 0);
64
65 // Reachable counts share the parity of 'lower', so select
66 // the matching tree.
67 const tree: AvlTree<number> = trees[lower % 2];
68
69 // Find the first unvisited value >= lower, then iterate
70 // through all values within [lower, upper], visiting and
71 // removing each one.
72 let node = tree.upperBound(lower, true);
73 while (node && node.getValue() <= upper) {
74 const value: number = node.getValue();
75 nextQueue.push(value);
76 tree.remove(value);
77 // Find the next unvisited value strictly greater than 'lower';
78 // removed values are skipped automatically.
79 node = tree.upperBound(lower, false);
80 }
81 }
82
83 // Advance to the next BFS level.
84 queue = nextQueue;
85 operations++;
86 }
87
88 // No sequence of operations reaches a zero-count of 0.
89 return -1;
90}
91Time and Space Complexity
-
Time Complexity:
O(n log n). The algorithm performs a BFS over the possible states, where each state corresponds to a value in the range[0, n](the number of '0's). Since each value can be visited at most once, there are at mostO(n)distinct states processed throughout the entire BFS. The twoSortedSetstructures initially hold allO(n)values, and each value is removed exactly once. Eachbisect_left, indexing, andremoveoperation on theSortedSetcostsO(log n). Because each element is added to the queue and removed from the set only once, the total work is bounded byO(n)operations each costingO(log n), giving an overall time complexity ofO(n log n). -
Space Complexity:
O(n). The twoSortedSetobjects together storeO(n)values (all integers from0ton). The BFS queueqcan hold at mostO(n)states at any time, since each value is enqueued at most once. Hence the total auxiliary space used isO(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Forgetting that the parity of the zero-count is invariant — and mishandling the "impossible" cases
The single most common mistake is treating this problem as if any target state can eventually be reached. In reality, every operation flips exactly k characters, so if an operation flips x zeros (turning them into ones) and k - x ones (turning them into zeros), the change in the zero-count is:
Δ = -x + (k - x) = k - 2x
Since 2x is always even, the parity of the zero-count changes by the parity of k on each operation. This has two consequences people routinely miss:
-
If
kis even, the parity of the zero-count never changes. So if you start with an odd number of zeros, you can never reach0(which is even) — the answer is-1immediately, no matter how many operations you perform. -
If
kis odd, the parity alternates every operation, so both even and odd counts are reachable over time.
A naive BFS that searches across both parity buckets without respecting this invariant will either:
- Explore unreachable states (wasting work, or worse, "reaching"
0through an invalid path if the range math is buggy), or - Fail to ever terminate correctly.
Why the provided code handles this correctly (and the subtle trap inside it)
The given solution sidesteps the issue elegantly: it computes left and right from the actual operation formula, and all states in [left, right] share the parity of left. It then only searches available_by_parity[left % 2]. Because the math is grounded in k - 2x, the parity invariant is enforced automatically — an even-k problem starting from an odd count will simply drain its queue without ever touching the even bucket, and the function returns -1.
The trap is if a developer "optimizes" or rewrites the expansion step and accidentally searches the wrong bucket, e.g.:
# WRONG: searches the parity of `current`, not `left` target_set = available_by_parity[current % 2]
After an odd-k operation, the reachable states have the opposite parity of current, so this lookup finds nothing valid (or the wrong things), silently breaking correctness.
Solution: assert the invariant explicitly and short-circuit the impossible case
Make the reasoning visible and bullet-proof by adding an early check, which also speeds up the obvious impossible case:
class Solution:
def minOperations(self, s: str, k: int) -> int:
length = len(s)
zero_count = s.count('0')
# Parity invariant: each operation changes the zero-count by (k - 2x),
# so the count shifts parity by (k % 2) each step.
# If k is even, parity is frozen. Reaching 0 (even) requires an
# even starting count; otherwise it is provably impossible.
if k % 2 == 0 and zero_count % 2 == 1:
return -1
# ... proceed with the BFS exactly as before ...
available_by_parity = [SortedSet() for _ in range(2)]
for value in range(length + 1):
available_by_parity[value % 2].add(value)
available_by_parity[zero_count % 2].remove(zero_count)
queue = deque([zero_count])
operations = 0
while queue:
for _ in range(len(queue)):
current = queue.popleft()
if current == 0:
return operations
left = current + k - 2 * min(current, k)
right = current + k - 2 * max(k - length + current, 0)
# Always key off `left`'s parity — this is the parity of the
# entire reachable range, NOT necessarily `current`'s parity.
target_set = available_by_parity[left % 2]
index = target_set.bisect_left(left)
while index < len(target_set) and target_set[index] <= right:
next_state = target_set[index]
queue.append(next_state)
target_set.remove(next_state)
operations += 1
return -1
Related secondary pitfalls
To round out the picture, here are three smaller mistakes worth guarding against:
-
Mutating the SortedSet while iterating by a fixed index. The original code relies on the fact that after
target_set.remove(...), the element that was atindex + 1shifts down intoindex, so it re-checks the sameindex. If you mistakenly writeindex += 1after a removal, you'll skip every other element in the range. Don't add the increment; the removal already advances you. -
Forgetting to remove the start state before the BFS. If
zero_countis left inavailable_by_parity, it can be re-discovered as a "next state" and re-enqueued, causing redundant work or, combined with a buggy visited check, an infinite loop. The lineavailable_by_parity[zero_count % 2].remove(zero_count)is essential. -
Off-by-one in the reachable range. The bounds come from clamping
x(zeros flipped) to[max(k - length + current, 0), min(current, k)]. Getting these clamps wrong — e.g., forgetting that you can't flip more ones than exist (length - current) or more zeros than exist (current) — produces an invalid range that either misses valid transitions or includes impossible ones. Verify with a tiny case likes = "010", k = 2:current = 2,left = 2 + 2 - 2*2 = 0,right = 2 + 2 - 2*max(2-3+2,0) = 4 - 2 = 2, giving range[0, 2]of even states — and0is reachable in one step, matching the expected answer of1.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich data structure is used to implement recursion?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro So far in our DFS discussions we have mostly dealt with graphs with all the nodes connected to each other and thus forming one connected component Let's now look at a more general case where
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Want a Structured Path to Master System Design Too? Don’t Miss This!