Special Binary String
Description
A binary string is called special when it follows two rules:
- It has the same number of
1s and0s. - No prefix has more
0s than1s. Reading from the left, the count of1s never falls behind the count of0s.
You are given a special binary string s. In one move, choose two non-empty special substrings of s that sit right next to each other (the first one ends exactly where the second one begins) and swap them.
You may make as many moves as you like, including none. Return the lexicographically largest string you can end up with. Comparing two strings of the same length from the left, the first position where they differ decides: the string with 1 there is larger.
It helps to draw the string as a walk. Read every 1 as one step up and every 0 as one step down. A special string is then a walk that starts on the ground, ends on the ground, and never goes below it. A special substring is a stretch that starts and ends at the same height and never dips below that height.

Examples
Example 1
Input: s = "10110010"
Output: "11001010"
Explanation: The walk touches the ground after the 2nd, 6th and 8th characters, so s is three special pieces side by side: "10", "1100", "10". The first two pieces are neighbours and both special, so one move swaps them, giving "1100" + "10" + "10" = "11001010". Every string you can reach is some order of these three pieces, and putting "1100" first is the only way to start with "11", so nothing beats it.
Example 2
Input: s = "101101101000"
Output: "111010010010"
Explanation: s is "10" followed by one big piece, "1101101000". That big piece is "1" + "10110100" + "0", and the part in between, "10110100", is special too: it is "10" followed by "110100".
- Swap those two inner pieces (positions 3–4 and 5–10): s becomes "10" + "1" + "110100" + "10" + "0" = "101110100100".
- Now the big piece is "1110100100". Swap it with the "10" in front of it: "1110100100" + "10" = "111010010010".
The inner swap had to happen too. The best string needs the big piece to be as large as possible and to come first.
Example 3
Input: s = "110100"
Output: "110100"
Explanation: s is a single piece, "1" + "1010" + "0", so there is nothing beside it to swap with. Inside it, "1010" is "10" + "10", and swapping those two pieces is the only move available. Because the pieces are identical, it gives back the same string. No move can ever change s.
Constraints
- 1 ≤ s.length ≤ 50
s[i]is either'0'or'1'.sis a special binary string.
Editorial
Brute Force
Intuition
The rules are simple to state but hard to picture, so the most direct plan is to try everything. Start from s, apply every possible move, then apply every possible move to each new string, and keep going until no new string appears. The largest string seen along the way is the answer.
Two pieces of work make this possible.
Listing every move of a string t. A move is fixed by three positions i < j < k where t[i:j] and t[j:k] are both special. To find every special substring that starts at i, walk right from i keeping a balance: +1 for each 1 and −1 for each 0.
- Each time the balance comes back to 0, the stretch so far is special.
- The moment the balance goes below 0, stop. No longer stretch starting at
ican be special, because its prefix already has more0s than1s.
Do this for every i to get every valid end j. Then, for each such j, every valid end k of a special substring starting at j gives one move.
Not doing the same work twice. Many moves lead back to strings we have already met. Swapping two identical pieces, for example, gives the same string back. So keep a set of seen strings and a queue of strings still to expand. Take a string off the queue, try all its moves, and add only the results that are new. This is a breadth-first search over strings.
Step-by-Step Explanation
We trace Example 1, s = "10110010". In each move the first piece is highlighted in amber and the second in indigo; the row underneath is the string after the swap.
Step 1: Put "10110010" in the queue and in the seen set. The best string so far is "10110010" itself.
Step 2: Expand "10110010": swap "10" (positions 0–1) with "1100" (positions 2–5) → "11001010". New — add it to the queue. The best is now "11001010".
Step 3: Swap "10" (positions 0–1) with "110010" (positions 2–7) → "11001010". Already seen — skip.
Step 4: Swap "101100" (positions 0–5) with "10" (positions 6–7) → "10101100". New — add it to the queue.
Step 5: Swap "1100" (positions 2–5) with "10" (positions 6–7) → "10101100". Already seen — skip.
Step 6: Expand "11001010": swap "1100" (positions 0–3) with "10" (positions 4–5) → "10110010". Already seen — skip.
Step 7: Swap "1100" (positions 0–3) with "1010" (positions 4–7) → "10101100". Already seen — skip.
Step 8: Swap "110010" (positions 0–5) with "10" (positions 6–7) → "10110010". Already seen — skip.
Step 9: Swap "10" (positions 4–5) with "10" (positions 6–7) → "11001010", the same string, because the pieces are equal. Nothing new.
Step 10: Expand "10101100": swap "10" (positions 0–1) with "10" (positions 2–3) → "10101100", the same string, because the pieces are equal. Nothing new.
Step 11: Swap "10" (positions 0–1) with "101100" (positions 2–7) → "10110010". Already seen — skip.
Step 12: Swap "1010" (positions 0–3) with "1100" (positions 4–7) → "11001010". Already seen — skip.
Step 13: Swap "10" (positions 2–3) with "1100" (positions 4–7) → "10110010". Already seen — skip.
Step 14: The queue is empty. Of the 3 strings reachable from "10110010", the largest is "11001010".
Brute Force — Every Move From Every Reachable String — Each step highlights one move: the amber piece and the indigo piece swap places, and the row below shows the string that results.
Algorithm
- Put
sin a queue and in a setseen. Letbest = s. - While the queue is not empty:
- Take the next string
toff the queue and setbest = max(best, t). - For every
i, list eachjsuch thatt[i:j]is special (balance back to 0, never below 0). - For every
i, every suchj, and everyksuch thatt[j:k]is special:- Build
u = t[:i] + t[j:k] + t[i:j] + t[k:]. - If
uis not inseen, add it toseenand to the queue.
- Build
- Take the next string
- Return
best.
Code
#include <queue>
#include <string>
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
// every j where t[i..j) is special: the balance returns to 0 without going below it
vector<int> specialEnds(const string& t, int i) {
vector<int> ends;
int balance = 0;
for (int j = i; j < (int)t.size(); j++) {
balance += t[j] == '1' ? 1 : -1;
if (balance < 0) break;
if (balance == 0) ends.push_back(j + 1);
}
return ends;
}
public:
string makeLargestSpecial(string s) {
unordered_set<string> seen{s};
queue<string> q;
q.push(s);
string best = s;
while (!q.empty()) {
string t = q.front();
q.pop();
best = max(best, t);
int n = t.size();
vector<vector<int>> ends(n + 1);
for (int i = 0; i < n; i++) ends[i] = specialEnds(t, i);
// a move swaps t[i..j) and t[j..k) when both are special
for (int i = 0; i < n; i++)
for (int j : ends[i])
for (int k : ends[j]) {
string u = t.substr(0, i) + t.substr(j, k - j) + t.substr(i, j - i) + t.substr(k);
if (seen.insert(u).second) q.push(u);
}
}
return best;
}
};from collections import deque
class Solution:
def makeLargestSpecial(self, s: str) -> str:
seen = {s}
queue = deque([s])
best = s
while queue:
t = queue.popleft()
best = max(best, t)
n = len(t)
ends = [self.special_ends(t, i) for i in range(n + 1)]
# a move swaps t[i:j] and t[j:k] when both are special
for i in range(n):
for j in ends[i]:
for k in ends[j]:
u = t[:i] + t[j:k] + t[i:j] + t[k:]
if u not in seen:
seen.add(u)
queue.append(u)
return best
def special_ends(self, t: str, i: int) -> list[int]:
# every j where t[i:j] is special: the balance returns to 0 without going below it
ends, balance = [], 0
for j in range(i, len(t)):
balance += 1 if t[j] == '1' else -1
if balance < 0:
break
if balance == 0:
ends.append(j + 1)
return endsimport java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Solution {
public String makeLargestSpecial(String s) {
Set<String> seen = new HashSet<>();
ArrayDeque<String> queue = new ArrayDeque<>();
seen.add(s);
queue.add(s);
String best = s;
while (!queue.isEmpty()) {
String t = queue.poll();
if (t.compareTo(best) > 0) best = t;
int n = t.length();
List<List<Integer>> ends = new ArrayList<>();
for (int i = 0; i <= n; i++) ends.add(specialEnds(t, i));
// a move swaps t[i..j) and t[j..k) when both are special
for (int i = 0; i < n; i++)
for (int j : ends.get(i))
for (int k : ends.get(j)) {
String u = t.substring(0, i) + t.substring(j, k) + t.substring(i, j) + t.substring(k);
if (seen.add(u)) queue.add(u);
}
}
return best;
}
// every j where t[i..j) is special: the balance returns to 0 without going below it
private List<Integer> specialEnds(String t, int i) {
List<Integer> ends = new ArrayList<>();
int balance = 0;
for (int j = i; j < t.length(); j++) {
balance += t.charAt(j) == '1' ? 1 : -1;
if (balance < 0) break;
if (balance == 0) ends.add(j + 1);
}
return ends;
}
}Complexity Analysis
Let n be the length of s and R the number of different strings reachable from it.
Time Complexity: O(R · n⁴). For each reachable string, listing the special ends takes O(n²). A string can have up to O(n³) moves, and building each resulting string takes O(n).
Space Complexity: O(R · n). Every reachable string is kept in the seen set.
Everything depends on R, and R can be enormous. That is the problem, as the next section shows.
Why This Approach Is Not Efficient
The search stores and expands every reachable string, and the number of reachable strings explodes.
Take the 50-character string built from 14 pieces standing side by side on the ground: seven "10", then four "1100", then the three larger mountains "111000", "110100" and "11110000". Neighbouring pieces can be swapped, so the 14 pieces can be arranged in 720,720 different orders, and each order is a different string the search must store and expand. Measured on exactly that input, the C++ version of this search ran for about 52 seconds and used 128 MB. Python is slower again: it already needs 1.7 s at 40 characters, where C++ needs 0.64 s.
The trace of Example 1 shows where the time goes. Of the 12 moves tried, only 2 produced a string we had not seen before. The rest shuffled the same three pieces back into orders already found.
There is something else in that trace. Every string reached was just a different order of the same three pieces — "10", "1100" and "10". No move ever cut a piece in half or mixed two pieces together. That is not luck, and the next approach is built on it.
Better Approach - Split Into Mountains, Try Every Order
Intuition
Look at the walk again. Each time it comes back down to the ground, one mountain ends. A special string is a row of mountains standing side by side. Every mountain has the same shape: a first step up (1), then some walk that never goes below that first step, then a final step down (0). The part in between, the mountain's inside, starts and ends at the same height and never dips below it, so the inside is a special string of its own.
Three facts about moves follow from this picture.
- A move never breaks a mountain. A special substring starts and ends at the same height and never dips below it, so it is always one or more whole mountains standing on the same floor. A move therefore swaps two neighbouring groups of whole mountains on one floor. It can change their order, but it cannot split a mountain or move part of one somewhere else. What happens inside a mountain stays inside it.
- Any order of the mountains on a floor can be reached. Two neighbouring mountains are two neighbouring special substrings, so swapping them is always a legal move. By swapping neighbours again and again, the way you would sort a hand of cards, you can put the mountains on a floor in any order you like.
- Every inside is its own smaller puzzle. The inside of a mountain is a special string, and moves inside it follow exactly the same rules, independently of everything else.

So the problem splits cleanly. Cut s into its mountains. Rebuild each mountain as "1" + best(inside) + "0", solving its inside with the very same function. Making a piece larger never hurts: with everything else fixed, a larger piece in the same place makes the whole string larger. Then choose the order of the pieces.
For that last choice, this approach simply tries every order. Pick which piece goes first, then find the best order of the rest the same way, and keep the largest result. When several pieces are identical, trying each of them first gives the same strings, so each different piece is tried only once in each position.
Step-by-Step Explanation
We trace Example 1 again, s = "10110010". The row shows the pieces; while orders are being tried, the row underneath shows the string that order produces.
Step 1: Split "10110010" where the balance returns to 0 (after positions 1, 5 and 7): the mountains are "10", "1100" and "10".
Step 2: Mountain 0, "10": its inside is empty — nothing to rearrange.
Step 3: Mountain 1, "1100": its inside "10" is special and already at its best, so the piece stays "1100".
Step 4: Mountain 2, "10": its inside is empty — nothing to rearrange.
Step 5: Order 1, "10" + "10" + "1100" = "10101100" — the best so far.
Step 6: Order 2, "10" + "1100" + "10" = "10110010" — larger than "10101100", the new best.
Step 7: Order 3, "1100" + "10" + "10" = "11001010" — larger than "10110010", the new best.
Step 8: Every distinct order has been tried. Three pieces can be lined up in 3! = 6 ways, but the two "10" pieces are identical, so only 3 strings are different. Return "11001010".
Mountains, Then Every Distinct Order — The string is cut into mountains, each mountain's inside is solved on its own, and then every distinct order of the pieces is tried.
Algorithm
best(s):
- Walk through
swith a balance (+1 for1, −1 for0). Each time the balance returns to 0, one mountains[start..i]has ended:- Its inside is
s[start+1..i-1]. Rebuild the piece as"1" + best(inside) + "0". - Start the next mountain at
i + 1.
- Its inside is
- Count how many copies of each different piece there are.
- best_order(pieces left): if none are left, return
"". Otherwise, for each different piece that still has a copy left, take one copy, formpiece + best_order(rest), put the copy back, and keep the largest result. - Return
best_order(all pieces).
Code
#include <map>
#include <string>
using namespace std;
class Solution {
// Try every distinct order: pick the piece that goes first, then order the rest the same way.
string bestOrder(map<string, int>& count, int left) {
if (left == 0) return "";
string best;
for (auto& [piece, copies] : count) {
if (copies == 0) continue;
copies--;
best = max(best, piece + bestOrder(count, left - 1));
copies++;
}
return best;
}
public:
string makeLargestSpecial(string s) {
map<string, int> count;
int balance = 0, start = 0, pieces = 0;
for (int i = 0; i < (int)s.size(); i++) {
balance += s[i] == '1' ? 1 : -1;
if (balance == 0) {
// s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
count["1" + makeLargestSpecial(s.substr(start + 1, i - start - 1)) + "0"]++;
pieces++;
start = i + 1;
}
}
return bestOrder(count, pieces);
}
};from collections import Counter
class Solution:
def makeLargestSpecial(self, s: str) -> str:
# A mountain ends wherever the balance returns to 0.
pieces, balance, start = [], 0, 0
for i, ch in enumerate(s):
balance += 1 if ch == '1' else -1
if balance == 0:
# s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
pieces.append('1' + self.makeLargestSpecial(s[start + 1:i]) + '0')
start = i + 1
return self.best_order(Counter(pieces), len(pieces))
def best_order(self, count: Counter, left: int) -> str:
# Try every distinct order: pick the piece that goes first, then order the rest the same way.
if left == 0:
return ''
best = ''
for piece in list(count):
if count[piece] > 0:
count[piece] -= 1
best = max(best, piece + self.best_order(count, left - 1))
count[piece] += 1
return bestimport java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
class Solution {
public String makeLargestSpecial(String s) {
Map<String, Integer> count = new HashMap<>();
int balance = 0, start = 0, pieces = 0;
for (int i = 0; i < s.length(); i++) {
balance += s.charAt(i) == '1' ? 1 : -1;
if (balance == 0) {
// s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
String piece = "1" + makeLargestSpecial(s.substring(start + 1, i)) + "0";
count.merge(piece, 1, Integer::sum);
pieces++;
start = i + 1;
}
}
return bestOrder(count, pieces);
}
// Try every distinct order: pick the piece that goes first, then order the rest the same way.
private String bestOrder(Map<String, Integer> count, int left) {
if (left == 0) return "";
String best = "";
for (String piece : new ArrayList<>(count.keySet())) {
int copies = count.get(piece);
if (copies == 0) continue;
count.put(piece, copies - 1);
String candidate = piece + bestOrder(count, left - 1);
if (candidate.compareTo(best) > 0) best = candidate;
count.put(piece, copies);
}
return best;
}
}Complexity Analysis
Let n be the length of s.
Time Complexity: the recursion into insides is cheap. Each floor is handled once, and each call scans its own part of the string. The cost is in trying orders. If a floor holds m pieces, and identical pieces appear c₁, c₂, … times, the number of different orders is m! / (c₁! × c₂! × …), where m! means 1 × 2 × … × m. Building each candidate costs up to O(n). That count grows factorially, faster than any power of m, as pieces are added to a floor.
Space Complexity: O(n²). The chain of unfinished calls is at most about n/2 deep, and each holds strings of length up to n.
Within this problem's limits it survives. The most orders a single floor of a 50-character string can have is 720,720, and on that input this approach took about 0.6 s in Python and 0.04 s in C++.
Why This Approach Is Not Efficient
Trying orders is still the wrong kind of work. The count grows factorially: 14 pieces on one floor already give 720,720 orders, and the same idea on a string of a few hundred characters would never finish. Almost all of that effort goes to orders that lose. In Example 1, three orders were tried to find one.
What we need is a rule that picks the best order directly. The traces already hint at it. The winning order in Example 1 put the largest piece first, "1100" ahead of the two "10"s. In Example 2, both on the ground and inside the big mountain, the winner again put the larger piece first.
The question is whether "largest first" is always right. For arbitrary strings it is not. Take the pieces "1" and "10". In dictionary order "10" is the larger one, since "1" is its beginning and a shorter beginning comes first. Yet largest first gives "10" + "1" = "101", while "1" + "10" = "110" is larger. The next approach shows why mountains can never fail like that.
Optimal Approach - Split Into Mountains, Sort Largest First
Intuition
Suppose two pieces stand next to each other, A first and then B, and B is larger than A. Swapping them is a legal move. Is the result larger?
Compare A + B with B + A. Neither mountain can be the beginning of the other. A mountain only touches the floor at its very last step, so if A were the start of B, the walk of B would touch the floor where A ends, before B's own end — impossible. So A and B differ somewhere inside both of them, and B has the 1 at the first place they differ. B + A and A + B differ first at that very same place, so B + A is larger. Everything before and after the pair is unchanged, so the whole string gets larger.
That settles it. In the best order, no piece can be followed by a larger one, or swapping the two would improve it. So the pieces must stand largest first, and that is exactly what sorting in descending order produces. This is also why the trap from the previous section cannot happen here. There, "1" was the beginning of "10", and mountains are never the beginning of one another.
The whole solution is the recursion from before, with sorting in place of trying orders:
best(s) = cut s into mountains, rebuild each as "1" + best(inside) + "0", sort the pieces from largest to smallest, and join them.
Step-by-Step Explanation
We trace Example 2, s = "101101101000". Each box is one call of best. A box is amber while it runs, indigo while it waits for one of its insides to be solved, and green, with its answer underneath, once it returns.
Step 1: best("101101101000") cuts the string where the balance returns to 0: "10" | "1101101000".
Step 2: Mountain "10" has an empty inside: best("") returns "" at once.
Step 3: Mountain "1101101000" has the inside "10110100". best("10110100") cuts it into "10" | "110100".
Step 4: Mountain "10" has an empty inside: best("") returns "" at once.
Step 5: Mountain "110100" has the inside "1010". best("1010") cuts it into "10" | "10".
Step 6: Mountain "10" has an empty inside: best("") returns "" at once.
Step 7: Mountain "10" has an empty inside: best("") returns "" at once.
Step 8: Back in best("1010"): the rebuilt pieces are "10", "10". Sorted largest first they give "1010".
Step 9: Back in best("10110100"): the rebuilt pieces are "10", "110100". Sorted largest first they give "11010010".
Step 10: Back in best("101101101000"): the rebuilt pieces are "10", "1110100100". Sorted largest first they give "111010010010".
best(s) — Solve Every Inside, Then Sort Each Floor — Each box is one call of best on a special string. A call waits while the insides of its mountains are solved, then sorts the rebuilt pieces and returns.
Algorithm
best(s):
- Set
balance = 0,start = 0, and an empty listpieces. - For each position
iins:- Add 1 to
balancefor a1, subtract 1 for a0. - If
balanceis 0, thens[start..i]is one mountain:- Append
"1" + best(s[start+1..i-1]) + "0"topieces. - Set
start = i + 1.
- Append
- Add 1 to
- Sort
piecesfrom largest to smallest. - Return the pieces joined together. For an empty
sthis is"".
Code
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
string makeLargestSpecial(string s) {
vector<string> pieces;
int balance = 0, start = 0;
for (int i = 0; i < (int)s.size(); i++) {
balance += s[i] == '1' ? 1 : -1;
if (balance == 0) {
// s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
pieces.push_back("1" + makeLargestSpecial(s.substr(start + 1, i - start - 1)) + "0");
start = i + 1;
}
}
// No mountain is the beginning of another, so plain descending order is the best order.
sort(pieces.begin(), pieces.end(), greater<string>());
string result;
for (const string& piece : pieces) result += piece;
return result;
}
};class Solution:
def makeLargestSpecial(self, s: str) -> str:
# A mountain ends wherever the balance returns to 0.
pieces, balance, start = [], 0, 0
for i, ch in enumerate(s):
balance += 1 if ch == '1' else -1
if balance == 0:
# s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
pieces.append('1' + self.makeLargestSpecial(s[start + 1:i]) + '0')
start = i + 1
# No mountain is the beginning of another, so plain descending order is the best order.
pieces.sort(reverse=True)
return ''.join(pieces)import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Solution {
public String makeLargestSpecial(String s) {
List<String> pieces = new ArrayList<>();
int balance = 0, start = 0;
for (int i = 0; i < s.length(); i++) {
balance += s.charAt(i) == '1' ? 1 : -1;
if (balance == 0) {
// s[start..i] is '1' + inside + '0', and the inside is special: solve it on its own
pieces.add("1" + makeLargestSpecial(s.substring(start + 1, i)) + "0");
start = i + 1;
}
}
// No mountain is the beginning of another, so plain descending order is the best order.
pieces.sort(Collections.reverseOrder());
return String.join("", pieces);
}
}Complexity Analysis
Let n be the length of s.
Time Complexity: O(n²).
- Scanning. Every call scans its own string once. A character is scanned once by each call whose string contains it, and there are at most n/2 such calls, one per level of nesting. So all the scanning together is at most n × n/2.
- Sorting. Sorting the pieces of one call compares strings whose lengths add up to that call's length. Over the whole recursion this adds no more than the same O(n²).
Space Complexity: O(n²) in the worst case. For a deeply nested string like "111…000", about n/2 calls are unfinished at once, and each holds its own copy of a string up to n long.
At full size the cost is negligible. On the 50-character input above with 720,720 orders on one floor, this version took about 0.02 s in Python, and the C++ run finished in under 0.01 s.