Facebook Pixel

1653. Minimum Deletions to Make String Balanced

Problem Description

You have a string s that contains only characters 'a' and 'b'.

A string is considered balanced when there are no positions where a 'b' appears before an 'a'. In other words, for any pair of indices (i,j) where i < j, if s[i] = 'b' and s[j] = 'a', the string is not balanced.

You can delete any number of characters from s to make it balanced. Your task is to find the minimum number of deletions needed to achieve this.

For example:

  • The string "aababbab" is not balanced because there are 'b' characters that appear before 'a' characters
  • The string "bbbbb" is balanced (all 'b's, no 'a' after any 'b')
  • The string "aaaa" is balanced (all 'a's)
  • The string "aaabbb" is balanced (all 'a's come before all 'b's)

The goal is to remove the fewest characters possible to ensure no 'b' comes before any 'a' in the resulting string.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To make the string balanced, we need to ensure no 'b' appears before any 'a'. Let's think about what happens as we process the string from left to right.

At each position, we need to make a decision based on the current character:

  1. If we encounter a 'b': This doesn't create any immediate problem. The string up to this point remains as balanced as it was before, since 'b' can appear at the end without issues.

  2. If we encounter an 'a': This is where we face a choice. Any 'b' that appeared before this 'a' creates an imbalance. We have two options:

    • Delete this 'a' to avoid the conflict (cost: 1 deletion)
    • Keep this 'a' but delete all previous 'b's (cost: number of 'b's seen so far)

The key insight is that we want to choose the option with minimum cost at each step.

This naturally leads to a dynamic programming approach where:

  • We track the minimum deletions needed to balance the string up to each position
  • We maintain a count of 'b's seen so far
  • For each 'a', we choose the minimum between deleting it or deleting all previous 'b's

The formula becomes:

  • When we see a 'b': f[i] = f[i-1] (no additional deletions needed)
  • When we see an 'a': f[i] = min(f[i-1] + 1, b) where b is the count of 'b's seen so far

This greedy choice at each step ensures we find the globally optimal solution with minimum deletions.

Learn more about Stack and Dynamic Programming patterns.

Solution Approach

The solution uses dynamic programming to track the minimum deletions needed at each position.

State Definition:

  • f[i] represents the minimum number of deletions needed to make the first i characters balanced
  • b counts the number of 'b' characters seen so far

Initialization:

  • f[0] = 0 (empty string is balanced)
  • b = 0 (no 'b's seen initially)

State Transition: We iterate through the string with index i from 1 to n:

  1. When s[i-1] = 'b':

    • The current 'b' doesn't create imbalance
    • f[i] = f[i-1] (no additional deletions)
    • Update b += 1 to track this 'b'
  2. When s[i-1] = 'a':

    • Option 1: Delete this 'a' → cost is f[i-1] + 1
    • Option 2: Keep this 'a' and delete all previous 'b's → cost is b
    • f[i] = min(f[i-1] + 1, b)

The state transition equation is:

f[i] = {
    f[i-1],                 if s[i-1] = 'b'
    min(f[i-1] + 1, b),     if s[i-1] = 'a'
}

Implementation Details:

def minimumDeletions(self, s: str) -> int:
    n = len(s)
    f = [0] * (n + 1)  # DP array
    b = 0              # Count of 'b's
  
    for i, c in enumerate(s, 1):  # 1-indexed iteration
        if c == 'b':
            f[i] = f[i - 1]
            b += 1
        else:  # c == 'a'
            f[i] = min(f[i - 1] + 1, b)
  
    return f[n]

Space Optimization: Since each state only depends on the previous state and variable b, we could optimize space to O(1) by using a single variable instead of an array, though the given solution uses O(n) space for clarity.

Time Complexity: O(n) - single pass through the string Space Complexity: O(n) - for the DP array

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the string s = "aababbab" to see how the algorithm finds the minimum deletions needed.

Initial Setup:

  • String: "aababbab" (length = 8)
  • DP array f = [0, 0, 0, 0, 0, 0, 0, 0, 0] (size 9, initialized to 0)
  • Counter b = 0 (tracks number of 'b's seen)

Step-by-step Processing:

Stepicharb beforeDecisionf[i] calculationf[i]b after
Init0-0--00
11'a'0Delete 'a' or delete 0 'b'smin(0+1, 0)00
22'a'0Delete 'a' or delete 0 'b'smin(0+1, 0)00
33'b'0Keep 'b'f[2]01
44'a'1Delete 'a' or delete 1 'b'min(0+1, 1)11
55'b'1Keep 'b'f[4]12
66'b'2Keep 'b'f[5]13
77'a'3Delete 'a' or delete 3 'b'smin(1+1, 3)23
88'b'3Keep 'b'f[7]24

Detailed Decisions:

  • Position 1-2 ('a', 'a'): No 'b's seen yet, so keeping these 'a's costs nothing
  • Position 3 ('b'): First 'b' appears, no conflict yet
  • Position 4 ('a'): This 'a' comes after 1 'b'. Either delete this 'a' (cost: 1) or delete the previous 'b' (cost: 1). Both equal, choose minimum = 1
  • Position 5-6 ('b', 'b'): More 'b's accumulate, no new conflicts
  • Position 7 ('a'): This 'a' comes after 3 'b's. Either delete this 'a' (cost: 1+1=2) or delete all 3 previous 'b's (cost: 3). Choose minimum = 2
  • Position 8 ('b'): Final 'b', no new conflict

Result: The minimum number of deletions needed is f[8] = 2

Verification: One optimal solution is to delete the two 'a's at positions 4 and 7, leaving "aabbbb" which is balanced (all 'a's before all 'b's).

Solution Implementation

1class Solution:
2    def minimumDeletions(self, s: str) -> int:
3        # Get the length of the input string
4        n = len(s)
5      
6        # dp[i] represents minimum deletions needed to make s[0:i] balanced
7        # We use n+1 size to handle 1-indexed iteration
8        dp = [0] * (n + 1)
9      
10        # Count of 'b' characters seen so far
11        b_count = 0
12      
13        # Iterate through each character with 1-based indexing
14        for i, char in enumerate(s, 1):
15            if char == 'b':
16                # If current character is 'b', no additional deletion needed
17                # The string remains balanced up to this point
18                dp[i] = dp[i - 1]
19                b_count += 1
20            else:
21                # If current character is 'a', we have two choices:
22                # 1. Delete this 'a' (cost: dp[i-1] + 1)
23                # 2. Keep this 'a' and delete all previous 'b's (cost: b_count)
24                dp[i] = min(dp[i - 1] + 1, b_count)
25      
26        # Return the minimum deletions needed for the entire string
27        return dp[n]
28
1class Solution {
2    public int minimumDeletions(String s) {
3        int length = s.length();
4      
5        // dp[i] represents the minimum deletions needed to make s[0..i-1] valid
6        // A valid string has all 'a's before all 'b's
7        int[] dp = new int[length + 1];
8      
9        // Count of 'b's encountered so far
10        int bCount = 0;
11      
12        // Iterate through each character in the string
13        for (int i = 1; i <= length; i++) {
14            char currentChar = s.charAt(i - 1);
15          
16            if (currentChar == 'b') {
17                // If current character is 'b', no need to delete it
18                // The minimum deletions remain the same as previous state
19                dp[i] = dp[i - 1];
20                bCount++;
21            } else {
22                // If current character is 'a', we have two choices:
23                // 1. Delete this 'a' (cost: dp[i-1] + 1)
24                // 2. Keep this 'a' and delete all previous 'b's (cost: bCount)
25                dp[i] = Math.min(dp[i - 1] + 1, bCount);
26            }
27        }
28      
29        // Return the minimum deletions needed for the entire string
30        return dp[length];
31    }
32}
33
1class Solution {
2public:
3    int minimumDeletions(string s) {
4        int stringLength = s.size();
5      
6        // dp[i] represents the minimum deletions needed to make s[0...i-1] balanced
7        // where balanced means no 'b' appears before any 'a'
8        vector<int> dp(stringLength + 1, 0);
9      
10        // Count of 'b' characters seen so far
11        int bCount = 0;
12      
13        // Iterate through each character in the string
14        for (int i = 1; i <= stringLength; ++i) {
15            if (s[i - 1] == 'b') {
16                // If current character is 'b', it doesn't violate the balance
17                // So minimum deletions remain the same as previous state
18                dp[i] = dp[i - 1];
19                bCount++;
20            } else {
21                // If current character is 'a', we have two choices:
22                // 1. Delete this 'a' (cost: dp[i-1] + 1)
23                // 2. Keep this 'a' and delete all 'b's before it (cost: bCount)
24                // Choose the minimum cost option
25                dp[i] = min(dp[i - 1] + 1, bCount);
26            }
27        }
28      
29        return dp[stringLength];
30    }
31};
32
1/**
2 * Finds the minimum number of deletions needed to make string balanced.
3 * A string is balanced if there are no 'b' characters before 'a' characters.
4 * 
5 * @param s - Input string containing only 'a' and 'b' characters
6 * @returns The minimum number of character deletions required
7 */
8function minimumDeletions(s: string): number {
9    const stringLength: number = s.length;
10  
11    // dp[i] represents minimum deletions needed to balance substring s[0...i-1]
12    const dp: number[] = new Array(stringLength + 1).fill(0);
13  
14    // Count of 'b' characters encountered so far
15    let bCount: number = 0;
16  
17    // Process each character in the string
18    for (let i = 1; i <= stringLength; ++i) {
19        if (s[i - 1] === 'b') {
20            // If current character is 'b', no additional deletion needed
21            // The minimum deletions remain the same as previous position
22            dp[i] = dp[i - 1];
23            ++bCount;
24        } else {
25            // If current character is 'a', we have two choices:
26            // 1. Delete this 'a' (cost: dp[i-1] + 1)
27            // 2. Keep this 'a' and delete all 'b's before it (cost: bCount)
28            dp[i] = Math.min(dp[i - 1] + 1, bCount);
29        }
30    }
31  
32    return dp[stringLength];
33}
34

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. The algorithm iterates through the string once with a single for loop, performing constant-time operations (comparisons, assignments, and min operations) for each character.

The space complexity is O(n), not O(1) as stated in the reference answer. The code creates an array f of size n + 1 to store the dynamic programming states, which requires linear extra space. The variable b uses constant space, but the dominant factor is the array f, making the overall space complexity O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Balance Condition

Pitfall: Many people initially think a balanced string means equal numbers of 'a's and 'b's, or that 'a's and 'b's must alternate. This leads to incorrect solutions that try to balance counts rather than positions.

Correct Understanding: A string is balanced when NO 'b' appears before ANY 'a'. Valid balanced strings include:

  • "aaabbb" (all 'a's before all 'b's)
  • "aaaa" (only 'a's)
  • "bbbb" (only 'b's)
  • "" (empty string)

2. Incorrect DP State Definition

Pitfall: Defining the DP state as "minimum deletions to balance the entire string up to index i" without properly tracking what characters can still appear after position i.

Solution: The DP state dp[i] should represent the minimum deletions needed to make the first i characters balanced, considering that any characters can follow after position i.

3. Off-by-One Indexing Errors

Pitfall: Mixing 0-based and 1-based indexing when accessing the string and DP array:

# Incorrect - index mismatch
for i in range(1, n + 1):
    if s[i] == 'b':  # ERROR: s[i] is out of bounds when i = n
        dp[i] = dp[i - 1]

Solution: Use enumerate with proper start value or be consistent with indexing:

# Correct approach 1: enumerate with start=1
for i, char in enumerate(s, 1):
    if char == 'b':
        dp[i] = dp[i - 1]

# Correct approach 2: 0-based indexing throughout
for i in range(n):
    if s[i] == 'b':
        dp[i + 1] = dp[i]

4. Not Considering All Options for 'a' Characters

Pitfall: When encountering an 'a', only considering deletion of that 'a' without realizing you could instead delete all previous 'b's:

# Incorrect - missing the option to delete previous b's
if char == 'a':
    dp[i] = dp[i - 1] + 1  # Only considers deleting the 'a'

Solution: Always consider both options:

if char == 'a':
    # Option 1: Delete this 'a'
    # Option 2: Keep this 'a' and delete all previous 'b's
    dp[i] = min(dp[i - 1] + 1, b_count)

5. Forgetting to Update the 'b' Counter

Pitfall: Forgetting to increment b_count when encountering a 'b' character, leading to incorrect cost calculation for keeping subsequent 'a's:

# Incorrect - forgot to update b_count
if char == 'b':
    dp[i] = dp[i - 1]
    # Missing: b_count += 1

Solution: Always update the counter when seeing a 'b':

if char == 'b':
    dp[i] = dp[i - 1]
    b_count += 1  # Don't forget this!

6. Space Optimization Gone Wrong

Pitfall: Attempting to optimize space to O(1) but incorrectly updating the same variable:

# Incorrect space optimization
def minimumDeletions(self, s: str) -> int:
    dp = 0
    b_count = 0
    for char in s:
        if char == 'b':
            # dp = dp (redundant)
            b_count += 1
        else:
            dp = min(dp + 1, b_count)  # This overwrites dp immediately
    return dp

Solution: If optimizing space, use a separate variable for the previous state:

def minimumDeletions(self, s: str) -> int:
    min_deletions = 0
    b_count = 0
    for char in s:
        if char == 'b':
            b_count += 1
        else:
            min_deletions = min(min_deletions + 1, b_count)
    return min_deletions
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More