3746. Minimum String Length After Balanced Removals
Problem Description
You are given a string s consisting only of the characters 'a' and 'b'.
You are allowed to repeatedly remove any substring where the number of 'a' characters is equal to the number of 'b' characters. After each removal, the remaining parts of the string are concatenated together without gaps.
Return an integer denoting the minimum possible length of the string after performing any number of such operations.
Example:
Suppose s = "aabbab". We can remove substrings whose count of 'a' equals the count of 'b'. For instance, we may remove "aabb" (2 a's and 2 b's), leaving "ab", and then remove "ab" (1 a and 1 b), leaving an empty string. The minimum possible length here is 0.
The key observation is that whenever two adjacent characters differ (one 'a' and one 'b'), they form a valid substring of equal counts and can be removed. By repeatedly removing such pairs, the string keeps shrinking until only one type of character remains. The leftover characters are simply the surplus of whichever character appears more frequently.
Therefore, if a is the count of 'a' and b is the count of 'b', the minimum possible length is the absolute difference between them, i.e. abs(a - b).
How We Pick the Algorithm
Why Stack?
This problem maps to Stack through a short path in the full flowchart.
The LIFO matching behavior of brackets or elements maps to stack operations.
Open in FlowchartIntuition
The core idea is to figure out which characters can actually be eliminated and which ones must remain no matter what we do.
Notice that whenever we have two adjacent characters that are different — that is, an 'a' next to a 'b' (or a 'b' next to an 'a') — they form a substring with exactly one 'a' and one 'b'. Since their counts are equal, this substring can be removed. After removing it, the characters that were on either side become adjacent, possibly creating new removable pairs.
This means we can keep "canceling out" pairs of different characters, much like matching opposite signs. Think of it as pushing characters together: every time an 'a' meets a 'b', they annihilate each other. So the question becomes — after canceling as many 'a'-'b' pairs as possible, what is left?
If we have a copies of 'a' and b copies of 'b', each removal consumes exactly one of each. We can keep removing until one type runs out. The character that appears more often will have some leftover copies that have nothing to pair with, and these can never be removed because any remaining substring would consist of only one type of character (which can never have equal counts unless it's empty).
So the final string consists purely of the surplus characters of whichever letter is more frequent. The number of these leftover characters is exactly abs(a - b), which is our minimum possible length. This also explains why the actual order of characters in s does not matter — only the counts of 'a' and 'b' determine the answer.
Pattern Learn more about Stack patterns.
Solution Approach
Solution 1: Counting
Based on the intuition above, the order of characters doesn't matter — only how many 'a' and 'b' characters exist. Every 'a'-'b' pair can eventually be canceled out, so all that remains is the surplus of the more frequent character. This reduces the entire problem to a simple counting task.
The implementation follows these steps:
-
Count the
'a'characters. Uses.count("a")to get the total number of'a'characters and store it ina. -
Derive the
'b'count. Since the string contains only'a'and'b', the number of'b'characters is simply the total length minus the count of'a', i.e.b = len(s) - a. This avoids scanning the string a second time. -
Return the surplus. The minimum possible length equals the absolute difference between the two counts,
abs(a - b), which represents the leftover characters that can never be paired and removed.
No extra data structures or complex patterns are needed — just two counts and one subtraction.
The implementation is shown below:
class Solution:
def minLengthAfterRemovals(self, s: str) -> int:
a = s.count("a")
b = len(s) - a
return abs(a - b)
Complexity Analysis:
- Time Complexity:
O(n), wherenis the length of the strings. Counting the'a'characters requires a single pass over the string. - Space Complexity:
O(1), since only a constant number of integer variables are used regardless of the input size.
Example Walkthrough
Let's trace through the solution approach with a small example: s = "abba".
Step 1: Understand what we're working with
The string s = "abba" has the following characters:
Index: 0 1 2 3 Char: a b b a
Step 2: Count the 'a' characters
We call s.count("a"). Scanning the string, we find 'a' at index 0 and index 3:
a = 2
Step 3: Derive the 'b' count
Rather than scanning again, we compute the number of 'b' characters using the total length:
b = len(s) - a = 4 - 2 = 2
Step 4: Return the surplus
The minimum possible length is the absolute difference between the two counts:
abs(a - b) = abs(2 - 2) = 0
So the answer is 0.
Why this matches the intuition (verifying by hand)
Let's confirm by actually performing removals on "abba":
- The middle two characters
"bb"? No — that has 2 b's and 0 a's, not removable. - Look at indices
1and2... but a better target is the substring"abba"itself: it contains 2 a's and 2 b's, so it's a valid removal. Removing it leaves the empty string"".
Alternatively, removing pairs gradually:
- Remove
"ab"(indices 0–1):"abba"→"ba" - Remove
"ba"(a valid pair, 1 'a' and 1 'b'):"ba"→""
Either path reaches an empty string of length 0, which exactly equals our computed abs(a - b) = 0. Since the counts of 'a' and 'b' are equal, every character finds a partner to cancel with, leaving nothing behind — confirming that only the counts (not the order) determine the result.
Solution Implementation
1class Solution:
2 def minLengthAfterRemovals(self, s: str) -> int:
3 # Count the number of 'a' characters in the string
4 count_a = s.count("a")
5
6 # The remaining characters are treated as 'b'
7 count_b = len(s) - count_a
8
9 # The minimum remaining length is the absolute difference
10 # between the two counts, since each pair of differing
11 # characters can be removed together
12 return abs(count_a - count_b)
13```
14
15**A note on perspective:**
16
17The original variable names (`a`, `b`) suggested the string consists only of two character types. If the real problem involves a stack-based removal of specific adjacent patterns (e.g., removing `"ab"` or `"ba"` substrings), a more general solution would use a stack:
18
19```python3
20class Solution:
21 def minLengthAfterRemovals(self, s: str) -> int:
22 # Use a stack to simulate removing adjacent matching pairs
23 stack: list[str] = []
24 for char in s:
25 # If the current char forms a removable pair with the top,
26 # pop it instead of pushing
27 if stack and stack[-1] != char:
28 stack.pop()
29 else:
30 stack.append(char)
31 # Remaining elements could not be paired off
32 return len(stack)
331class Solution {
2 /**
3 * Counts how many characters remain after repeatedly removing pairs.
4 * The string is conceptually split into two groups: 'a' characters and
5 * non-'a' characters. Each removal pairs one character from each group,
6 * so the number left over equals the absolute difference of the two counts.
7 *
8 * @param s the input string to process
9 * @return the minimum length remaining after all possible removals
10 */
11 public int minLengthAfterRemovals(String s) {
12 int length = s.length();
13
14 // Count the number of 'a' characters in the string.
15 int countA = 0;
16 for (int i = 0; i < length; i++) {
17 if (s.charAt(i) == 'a') {
18 countA++;
19 }
20 }
21
22 // The remaining characters belong to the non-'a' group.
23 int countOthers = length - countA;
24
25 // After pairing characters from both groups, the leftover count
26 // is the absolute difference between the two group sizes.
27 return Math.abs(countA - countOthers);
28 }
29}
301class Solution {
2public:
3 int minLengthAfterRemovals(string s) {
4 // Count the number of 'a' characters in the string
5 int countA = 0;
6 for (char ch : s) {
7 if (ch == 'a') {
8 ++countA;
9 }
10 }
11
12 // The remaining characters are all non-'a' characters
13 int countOther = static_cast<int>(s.size()) - countA;
14
15 // Return the absolute difference between the two counts
16 return abs(countA - countOther);
17 }
18};
191/**
2 * Calculates the minimum length of a string after performing removals.
3 *
4 * The string characters are partitioned into two groups:
5 * - characters equal to 'a'
6 * - all other characters
7 * Pairs from opposing groups cancel each other out, so the final
8 * remaining length is the absolute difference between the two counts.
9 *
10 * @param s - The input string to process.
11 * @returns The minimum length remaining after removals.
12 */
13function minLengthAfterRemovals(s: string): number {
14 // Count of characters equal to 'a'.
15 let countA = 0;
16
17 // Iterate through each character and tally the 'a' occurrences.
18 for (const char of s) {
19 if (char === 'a') {
20 ++countA;
21 }
22 }
23
24 // Count of all characters that are not 'a'.
25 const countOther = s.length - countA;
26
27 // The unmatched remainder equals the absolute difference of the two groups.
28 return Math.abs(countA - countOther);
29}
30Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the strings. The operations.count("a")traverses the entire string once to count the occurrences of the character"a", takingO(n)time. Computinglen(s)isO(1), and the remaining arithmetic operations (subtraction andabs) are alsoO(1). Therefore, the overall time complexity is dominated by the counting operation, resulting inO(n). -
Space Complexity:
O(1). The algorithm only uses a constant amount of extra space to store the variablesaandb, regardless of the input size. No additional data structures that scale with the input are used.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Assuming Adjacency Constraints Limit Removals
The most common mistake is over-engineering the solution with a stack, believing that only adjacent differing pairs (like "ab" or "ba") can be removed. The problem actually allows removing any substring where the count of 'a' equals the count of 'b'—not just adjacent opposite characters.
This distinction matters: the stack approach assumes characters can only cancel when they meet at the top of the stack, but in reality, the order is irrelevant. Consider s = "aabb":
- Stack approach result: push
'a', push'a', then'b'cancels one'a', then'b'cancels another'a'→ length0. ✓ (happens to work here) - But for
s = "abba": push'a', push'b'cancels'a', push'b', push'a'cancels'b'→ length0. ✓
While the stack often produces the same numeric answer because every 'a' eventually finds a 'b', treating this as a constrained adjacency problem leads to confusion and unnecessary complexity. The correct mental model is pure counting.
Solution: Recognize that since the whole string is a valid removal target whenever counts are balanced, only the surplus survives. Stick with abs(count_a - count_b).
Pitfall 2: Scanning the String Twice
A subtle inefficiency is counting both characters separately:
# Less efficient — two passes
count_a = s.count("a")
count_b = s.count("b")
return abs(count_a - count_b)
Since the string contains only 'a' and 'b', the second scan is redundant.
Solution: Derive the 'b' count arithmetically from the total length:
count_a = s.count("a")
count_b = len(s) - count_a # single pass
return abs(count_a - count_b)
Pitfall 3: Forgetting the Absolute Value
Returning count_a - count_b directly can yield a negative result when 'b' characters outnumber 'a' characters (e.g., s = "abbb" gives 1 - 3 = -2). A length can never be negative.
Solution: Always wrap the difference in abs():
return abs(count_a - count_b)
Pitfall 4: Mishandling Edge Cases
Failing to account for boundary inputs can cause errors:
- Empty string (
s = ""): both counts are0, correctly returning0. - Single character (
s = "a"): returnsabs(1 - 0) = 1, which is correct since a lone character can never be paired. - All identical characters (
s = "aaaa"): returnsabs(4 - 0) = 4, as no removals are possible.
Solution: The counting formula naturally handles all these cases without special branching—verify them mentally rather than adding redundant guard clauses.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhat's the output of running the following function using the following tree as input?

1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
121import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
181function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16Recommended Readings
Stack Intro Following the Foundation Course Stay on that path and use the Foundation Course Stack module courses foundation stack_lifo_model instead This page is a quick Core Patterns refresher for students who already know the basics Imagine you have a pile of books on your desk If you want to add a
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!