Before attempting this problem, you should be comfortable with:
Stack Data Structure - Used to track unmatched opening brackets during traversal
Bracket Matching - Understanding how to pair opening and closing brackets in sequence
Greedy Algorithms - The optimal solutions use greedy reasoning to count minimum swaps needed
1. Stack
Intuition
A balanced string has every ] matched with a preceding [. We use a stack to track unmatched opening brackets. When we see [, we push it. When we see ] and the stack is not empty, we pop (the bracket is matched). If the stack is empty when we see ], that closing bracket is unmatched.
After processing, the stack contains only unmatched [ brackets. Since the string has equal counts of [ and ], the number of unmatched [ equals the number of unmatched ]. Each swap fixes two unmatched pairs, so we need (unmatched + 1) / 2 swaps.
Algorithm
Initialize an empty stack.
Iterate through the string:
If the character is [, push it onto the stack.
If the character is ] and the stack is not empty, pop the stack.
The remaining stack size represents unmatched [ brackets.
classSolution:defminSwaps(self, s:str)->int:
stack =[]for c in s:if c =='[':
stack.append(c)elif stack:
stack.pop()return(len(stack)+1)//2
publicclassSolution{publicintminSwaps(String s){Stack<Character> stack =newStack<>();for(char c : s.toCharArray()){if(c =='['){
stack.push(c);}elseif(!stack.isEmpty()){
stack.pop();}}return(stack.size()+1)/2;}}
classSolution{public:intminSwaps(string s){
vector<char> stack;for(char c : s){if(c =='['){
stack.push_back(c);}elseif(!stack.empty()){
stack.pop_back();}}return(stack.size()+1)/2;}};
classSolution{/**
* @param {string} s
* @return {number}
*/minSwaps(s){let stack =[];for(const c of s){if(c ==='['){
stack.push(c);}elseif(stack.length >0){
stack.pop();}}return Math.floor((stack.length +1)/2);}}
publicclassSolution{publicintMinSwaps(string s){Stack<char> stack =newStack<char>();foreach(char c in s){if(c =='['){
stack.Push(c);}elseif(stack.Count >0){
stack.Pop();}}return(stack.Count +1)/2;}}
funcminSwaps(s string)int{
stack :=0for_, c :=range s {if c =='['{
stack++}elseif stack >0{
stack--}}return(stack +1)/2}
class Solution {funminSwaps(s: String): Int {val stack = ArrayDeque<Char>()for(c in s){if(c =='['){
stack.addLast(c)}elseif(stack.isNotEmpty()){
stack.removeLast()}}return(stack.size +1)/2}}
classSolution{funcminSwaps(_ s:String)->Int{var stack =[Character]()for c in s {if c =="["{
stack.append(c)}elseif!stack.isEmpty {
stack.removeLast()}}return(stack.count +1)/2}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
2. Greedy - I
Intuition
Instead of tracking opening brackets, we can track the imbalance directly. We maintain a close counter that increases for ] and decreases for [. The max value this counter reaches tells us the worst-case imbalance, meaning the maximum number of unmatched closing brackets at any point.
Since each swap can fix at most 2 unmatched brackets, the number of swaps needed is (max_imbalance + 1) / 2.
classSolution:defminSwaps(self, s:str)->int:
close = maxClose =0for c in s:if c =='[':
close -=1else:
close +=1
maxClose =max(maxClose, close)return(maxClose +1)//2
publicclassSolution{publicintminSwaps(String s){int close =0, maxClose =0;for(int i =0; i < s.length(); i++){if(s.charAt(i)=='[') close--;else close++;
maxClose =Math.max(maxClose, close);}return(maxClose +1)/2;}}
classSolution{public:intminSwaps(string s){int close =0, maxClose =0;for(auto& c : s){if(c =='[') close--;else close++;
maxClose =max(maxClose, close);}return(maxClose +1)/2;}};
classSolution{/**
* @param {string} s
* @return {number}
*/minSwaps(s){let close =0,
maxClose =0;for(let i =0; i < s.length; i++){if(s.charAt(i)=='[') close--;else close++;
maxClose = Math.max(maxClose, close);}return Math.floor((maxClose +1)/2);}}
publicclassSolution{publicintMinSwaps(string s){int close =0, maxClose =0;foreach(char c in s){if(c =='[') close--;else close++;
maxClose = Math.Max(maxClose, close);}return(maxClose +1)/2;}}
funcminSwaps(s string)int{close, maxClose :=0,0for_, c :=range s {if c =='['{close--}else{close++}ifclose> maxClose {
maxClose =close}}return(maxClose +1)/2}
class Solution {funminSwaps(s: String): Int {var close =0var maxClose =0for(c in s){if(c =='[') close--else close++
maxClose =maxOf(maxClose, close)}return(maxClose +1)/2}}
classSolution{funcminSwaps(_ s:String)->Int{var close =0var maxClose =0for c in s {if c =="["{
close -=1}else{
close +=1}
maxClose =max(maxClose, close)}return(maxClose +1)/2}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
3. Greedy - II
Intuition
This approach directly simulates the stack without actually using a stack data structure. We use a counter stackSize that increments for [ and decrements for ] only if there is something to match (stackSize > 0). The final counter value represents unmatched opening brackets.
This is equivalent to the stack approach but uses O(1) space since we only track the count, not the actual characters.
Algorithm
Initialize stackSize = 0.
Iterate through the string:
If the character is [, increment stackSize.
If the character is ] and stackSize > 0, decrement stackSize.
classSolution{/**
* @param {string} s
* @return {number}
*/minSwaps(s){let stackSize =0;for(let i =0; i < s.length; i++){if(s.charAt(i)=='[') stackSize++;elseif(stackSize >0) stackSize--;}return Math.floor((stackSize +1)/2);}}
publicclassSolution{publicintMinSwaps(string s){int stackSize =0;foreach(char c in s){if(c =='[') stackSize++;elseif(stackSize >0) stackSize--;}return(stackSize +1)/2;}}
funcminSwaps(s string)int{
stackSize :=0for_, c :=range s {if c =='['{
stackSize++}elseif stackSize >0{
stackSize--}}return(stackSize +1)/2}
class Solution {funminSwaps(s: String): Int {var stackSize =0for(c in s){if(c =='[') stackSize++elseif(stackSize >0) stackSize--}return(stackSize +1)/2}}
classSolution{funcminSwaps(_ s:String)->Int{var stackSize =0for c in s {if c =="["{
stackSize +=1}elseif stackSize >0{
stackSize -=1}}return(stackSize +1)/2}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
Common Pitfalls
Counting All Brackets Instead of Unmatched
The answer depends on unmatched brackets, not total brackets. Counting all [ or ] characters without first matching valid pairs will give an incorrect count. Only brackets that remain after matching contribute to the swap count.
Forgetting That One Swap Fixes Two Pairs
Each swap can fix two unmatched bracket pairs simultaneously. Returning the unmatched count directly instead of dividing by 2 (with ceiling) will double the actual number of swaps needed.
Popping From Empty Stack
When simulating with a stack, attempting to pop when encountering ] without checking if the stack is empty will cause runtime errors. Only pop if there is a matching [ available to pair with the current ].