Problem Overview
LeetCode 1963 Minimum Number of Swaps to Make the String Balanced gives you a string of only "[" and "]" characters with an equal number of each. You are allowed to swap any two characters in the string any number of times. The goal is to return the minimum number of swaps required to make the string balanced.
A balanced string is one where every prefix has at least as many open brackets as close brackets — equivalently, no "]" ever appears before its matching "[". Because the total counts of "[" and "]" are equal, a balanced string here means the string can be fully matched into valid bracket pairs.
The naive approach of trying all possible swaps is exponential. The key insight is that you do not need to simulate swaps at all — a single greedy scan lets you count unmatched brackets, and the answer follows directly from that count via a simple formula.
- String of "[" and "]" only, with equal counts of each character
- Swap any two positions in the string per operation
- Return the minimum number of swaps to make the string balanced
- Balanced means every prefix has open count >= close count
Cancel Matched Pairs
Scan the string from left to right while maintaining a counter for unmatched open brackets. When you encounter "[", increment the open counter — this bracket is potentially matchable. When you encounter "]", check if the open counter is greater than zero: if so, decrement it to cancel a matched pair. If open is zero, this "]" has no matching "[" before it and is unmatched.
After the full scan, the unmatched close brackets (the ones we counted) are all to the left of the unmatched open brackets. This is because we cancelled every "[" that could be matched by a preceding or subsequent "]" in natural order. What remains forms a suffix of the form "]]]..[[[" — all unmatched closes then all unmatched opens.
The number of unmatched closes equals the number of unmatched opens (since the total counts are equal). Call this count "unmatched" (= the close count from our scan, which equals the final open count). The problem reduces to: how many swaps does it take to fix a sequence of k unmatched closes followed by k unmatched opens?
After Cancellation You Have "]]]...[[[" Remaining
After cancelling all naturally matched pairs, you are left with "]]]...[[[" — all closes before all opens. Each swap of "][" into "[]" fixes two unmatched pairs simultaneously. So the answer is ceil(unmatched / 2), where unmatched is the count of remaining unmatched close brackets (which equals the count of remaining unmatched open brackets).
Count Unmatched Brackets
The counting algorithm uses just two integer variables: open and close, both starting at zero. Iterate through every character in the string. When the character is "[", increment open. When the character is "]" and open is greater than zero, decrement open (a match was made). When the character is "]" and open is zero, increment close (this close bracket is unmatched).
At the end of the scan, open and close will be equal (because the input guarantees equal total counts of "[" and "]"). The variable close holds the number of unmatched close brackets, which is also the number of unmatched open brackets. This is the value we use in the formula.
The entire counting step runs in O(n) time and O(1) space — no stack, no auxiliary array, no sorting. Just two counters updated in a single left-to-right pass over the string.
- 1open = 0, close = 0
- 2For each char in s:
- 3 If char == "[": open++
- 4 If char == "]" and open > 0: open-- (cancel matched pair)
- 5 If char == "]" and open == 0: close++ (unmatched close)
- 6After loop: unmatched = close (equals open by guarantee)
- 7Answer = (close + 1) // 2
Why ceil(unmatched / 2)
After cancellation, the unmatched portion looks like: "]]]..[[[" with k unmatched closes followed by k unmatched opens. The leftmost "]" and rightmost "[" are the perfect candidates for a swap — swapping them produces "[[...]]" which fixes one "]" that was too early and one "[" that was too late, simultaneously resolving two unmatched brackets at once.
Each such swap reduces the unmatched count by exactly 2 — it converts one unmatched "]" into "[" (now a valid open before many closes) and one unmatched "[" into "]" (now a valid close after many opens). This means k pairs of unmatched brackets can be fixed with k/2 swaps if k is even, or (k+1)/2 swaps if k is odd (the last swap fixes only one remaining unmatched pair when k is odd, though it still only takes one swap).
The formula (unmatched + 1) // 2 in integer arithmetic gives ceil(unmatched / 2). This is the minimum — no strategy can do better because each swap can resolve at most 2 unmatched brackets, so you need at least ceil(k/2) swaps to eliminate k unmatched brackets.
One Swap Converts "][" to "[]" Fixing Two Unmatched
One swap converts "][" to "[]": this fixes both one unmatched "]" (it becomes "[", a valid open) and one unmatched "[" (it becomes "]", a valid close) simultaneously. Hence each swap resolves 2 unmatched brackets. With k unmatched brackets total, the minimum swaps needed is ceil(k / 2) = (k + 1) // 2.
Walk-Through Example
Consider s = "][][". Trace the cancellation pass: index 0 is "]", open=0 so close=1; index 1 is "[", open=1; index 2 is "]", open>0 so open=0 (cancel); index 3 is "[", open=1. End state: open=1, close=1. Unmatched = 1 (one unmatched close and one unmatched open remain).
The answer is ceil(1 / 2) = 1 swap. Verify: swap index 0 ("]") with index 3 ("["): we get "[][][" — wait, s has length 4. Swapping positions 0 and 3 turns "][][" into "[]][" — that is still not balanced. Let us swap positions 0 and 1 instead: "[][]" — balanced in 1 swap. Answer confirmed = 1.
Now consider s = "]][[". Trace: index 0 "]" → close=1; index 1 "]" → close=2; index 2 "[" → open=1; index 3 "[" → open=2. End state: open=2, close=2. Unmatched=2. Answer = ceil(2/2) = 1 swap. Swap index 0 and index 3: "[[]]" — balanced in 1 swap. Confirmed.
- 1s = "][][": open=0, close=0
- 2Index 0 "]": open==0 → close=1
- 3Index 1 "[": open=1
- 4Index 2 "]": open>0 → open=0 (cancel)
- 5Index 3 "[": open=1
- 6End: close=1, open=1 → unmatched=1
- 7Answer = (1 + 1) // 2 = 1 swap
- 8Swap index 0 and 1: "[][]" — balanced, confirmed
Code Walkthrough — Python and Java
Python solution: def minSwaps(s: str) -> int: open = 0; close = 0; for c in s: if c == "[": open += 1; elif open > 0: open -= 1; else: close += 1; return (close + 1) // 2. The loop runs in O(n) and the two variables give O(1) space. The return expression uses integer division to compute ceil(close / 2).
Java solution: public int minSwaps(String s) { int open = 0, close = 0; for (char c : s.toCharArray()) { if (c == "[") open++; else if (open > 0) open--; else close++; } return (close + 1) / 2; } This is identical logic in Java. The (close + 1) / 2 expression performs integer floor division which equals ceil(close / 2) for non-negative integers.
Both solutions are O(n) time and O(1) space. The only difference from a naive stack-based solution is that we use a counter instead of an actual stack — since we only ever need to know whether the stack is empty (open > 0) and what its size is (the open counter), no actual stack data structure is necessary.
Do Not Use a Stack — Use a Counter Instead
Don't use an actual stack to track unmatched brackets: it works correctly but wastes O(n) space. Since you only need to know if the stack is non-empty and how many items are in it, a single integer counter for open brackets gives identical results in O(1) space. The counter approach is both simpler and more memory-efficient.