Facebook Pixel

1221. Split a String in Balanced Strings

EasyGreedyStringCounting
Leetcode Link

Problem Description

You are given a balanced string s that contains equal numbers of 'L' and 'R' characters.

Your task is to split this string into the maximum number of smaller balanced substrings. Each substring must also be balanced (having equal numbers of 'L' and 'R' characters).

For example:

  • The string "RLRRLLRLRL" can be split into "RL", "RRLL", "RL", "RL" - giving us 4 balanced substrings
  • The string "RLLLLRRRLR" can be split into "RL", "LLLRRR", "LR" - giving us 3 balanced substrings

The goal is to find the maximum number of such balanced substrings you can create from the original string.

The solution uses a greedy approach with a counter variable l that tracks the balance:

  • When encountering 'L', increment the counter by 1
  • When encountering 'R', decrement the counter by 1
  • Whenever the counter reaches 0, we've found a balanced substring

This greedy strategy works because taking the smallest possible balanced substring at each step maximizes the total count of substrings.

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

Intuition

The key insight is that we want to maximize the number of balanced substrings, which means we should create the smallest possible balanced substrings whenever we can. Why? Because if we combine two balanced substrings into one larger balanced substring, we reduce our total count from 2 to 1.

Think of it like cutting a rope into pieces - if you want the maximum number of pieces, you should make each cut as soon as possible rather than waiting.

We can track balance using a simple counter. Imagine walking through the string and keeping a running tally:

  • Each 'L' adds +1 to our balance
  • Each 'R' adds -1 to our balance

When this counter returns to 0, we know we've seen equal numbers of 'L' and 'R' characters since our last balanced point. This forms a complete balanced substring.

For example, with "RLRRLLRLRL":

  • Start with counter = 0
  • 'R': counter = -1
  • 'L': counter = 0 β†’ Found balanced substring "RL", count = 1
  • 'R': counter = -1
  • 'R': counter = -2
  • 'L': counter = -1
  • 'L': counter = 0 β†’ Found balanced substring "RRLL", count = 2
  • And so on...

The greedy approach works because once we find a balanced substring, there's no advantage to waiting and combining it with future characters. Taking it immediately guarantees we maximize our count.

Learn more about Greedy patterns.

Solution Approach

The implementation uses a greedy algorithm with a simple counter to track the balance of 'L' and 'R' characters.

Variables Used:

  • ans: Keeps track of the total number of balanced substrings found
  • l: Acts as a balance counter to track the difference between 'L' and 'R' characters

Algorithm Steps:

  1. Initialize both ans and l to 0

  2. Iterate through each character c in the string s:

    • If c == 'L': increment l by 1 (adding to the balance)
    • If c == 'R': decrement l by 1 (subtracting from the balance)
  3. After updating l, check if l == 0:

    • If true, we've found a balanced substring
    • Increment ans by 1
  4. Return ans as the maximum number of balanced substrings

Why This Works:

The counter l maintains the running balance at any point:

  • A positive value means we have more 'L's than 'R's so far
  • A negative value means we have more 'R's than 'L's so far
  • Zero means we have equal 'L's and 'R's, forming a balanced substring

By greedily counting a balanced substring every time l returns to 0, we ensure we're creating the smallest possible balanced substrings at each opportunity, which maximizes the total count.

Time Complexity: O(n) where n is the length of the string - we traverse the string once

Space Complexity: O(1) - we only use two variables regardless of input size

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 walk through the string "RLLR" step by step to see how our algorithm finds the maximum number of balanced substrings.

Initial Setup:

  • String: "RLLR"
  • ans = 0 (count of balanced substrings)
  • l = 0 (balance counter)

Step-by-Step Execution:

Step 1: Process 'R' at index 0

  • Since it's 'R', we decrement: l = 0 - 1 = -1
  • l β‰  0, so no balanced substring yet
  • Current state: ans = 0, l = -1

Step 2: Process 'L' at index 1

  • Since it's 'L', we increment: l = -1 + 1 = 0
  • l == 0 βœ“ We found a balanced substring "RL"!
  • Increment answer: ans = 1
  • Current state: ans = 1, l = 0

Step 3: Process 'L' at index 2

  • Since it's 'L', we increment: l = 0 + 1 = 1
  • l β‰  0, so no balanced substring yet
  • Current state: ans = 1, l = 1

Step 4: Process 'R' at index 3

  • Since it's 'R', we decrement: l = 1 - 1 = 0
  • l == 0 βœ“ We found another balanced substring "LR"!
  • Increment answer: ans = 2
  • Current state: ans = 2, l = 0

Result: The string "RLLR" can be split into maximum of 2 balanced substrings: "RL" and "LR"

The balance counter l acts like a seesaw - when it tilts to one side (positive or negative), we know we have an imbalance. When it returns to level (zero), we've found equal 'L's and 'R's, forming a complete balanced substring. By counting each time we reach zero, we maximize the number of substrings.

Solution Implementation

1class Solution:
2    def balancedStringSplit(self, s: str) -> int:
3        """
4        Count the maximum number of balanced substrings in a string containing only 'L' and 'R'.
5        A balanced string has equal number of 'L' and 'R' characters.
6      
7        Args:
8            s: Input string containing only 'L' and 'R' characters
9          
10        Returns:
11            Maximum number of balanced substrings that can be split
12        """
13        # Initialize counter for balanced substrings and balance tracker
14        balanced_count = 0
15        balance = 0
16      
17        # Iterate through each character in the string
18        for char in s:
19            # Increment balance for 'L', decrement for 'R'
20            if char == 'L':
21                balance += 1
22            else:  # char == 'R'
23                balance -= 1
24          
25            # When balance reaches 0, we have a balanced substring
26            if balance == 0:
27                balanced_count += 1
28      
29        return balanced_count
30
1class Solution {
2    public int balancedStringSplit(String s) {
3        // Counter for the number of balanced substrings found
4        int balancedCount = 0;
5      
6        // Balance tracker: increments for 'L', decrements for 'R'
7        // When balance reaches 0, we have equal L's and R's
8        int balance = 0;
9      
10        // Iterate through each character in the string
11        for (char character : s.toCharArray()) {
12            // Update balance based on current character
13            if (character == 'L') {
14                balance++;
15            } else {  // character == 'R'
16                balance--;
17            }
18          
19            // When balance is 0, we've found a balanced substring
20            if (balance == 0) {
21                balancedCount++;
22            }
23        }
24      
25        return balancedCount;
26    }
27}
28
1class Solution {
2public:
3    int balancedStringSplit(string s) {
4        int balancedCount = 0;  // Counter for balanced substrings
5        int balance = 0;         // Track the balance between 'L' and 'R'
6      
7        // Iterate through each character in the string
8        for (char ch : s) {
9            // Increment balance for 'L', decrement for 'R'
10            if (ch == 'L') {
11                ++balance;
12            } else {
13                --balance;
14            }
15          
16            // When balance reaches 0, we have a balanced substring
17            if (balance == 0) {
18                ++balancedCount;
19            }
20        }
21      
22        return balancedCount;
23    }
24};
25
1/**
2 * Splits a balanced string into maximum number of balanced substrings
3 * @param s - Input string containing only 'L' and 'R' characters
4 * @returns The maximum number of balanced substrings
5 */
6function balancedStringSplit(s: string): number {
7    // Counter for the maximum number of balanced substrings
8    let balancedCount: number = 0;
9  
10    // Balance counter: increments for 'L', decrements for 'R'
11    // When balance reaches 0, we have a balanced substring
12    let balance: number = 0;
13  
14    // Iterate through each character in the string
15    for (const char of s) {
16        // Increment balance for 'L' character
17        if (char === 'L') {
18            balance++;
19        } else {
20            // Decrement balance for 'R' character
21            balance--;
22        }
23      
24        // When balance is 0, we've found a balanced substring
25        if (balance === 0) {
26            balancedCount++;
27        }
28    }
29  
30    return balancedCount;
31}
32

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. This is because the algorithm iterates through each character in the string exactly once using a single for loop, performing constant-time operations (O(1)) for each character (comparison, addition/subtraction, and conditional check).

The space complexity is O(1). The algorithm only uses a fixed amount of extra space regardless of the input size - specifically, two integer variables ans and l to keep track of the count of balanced substrings and the balance counter respectively. The space usage does not grow with the input size.

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

Common Pitfalls

1. Attempting to Find the Actual Substrings Instead of Just Counting

A common mistake is trying to store or track the actual balanced substrings themselves, which unnecessarily complicates the solution and increases space complexity.

Incorrect Approach:

def balancedStringSplit(self, s: str) -> int:
    substrings = []  # Unnecessary storage
    balance = 0
    start = 0
  
    for i, char in enumerate(s):
        if char == 'L':
            balance += 1
        else:
            balance -= 1
      
        if balance == 0:
            substrings.append(s[start:i+1])  # Storing actual substring
            start = i + 1
  
    return len(substrings)

Why It's a Problem: This uses O(n) extra space and adds unnecessary complexity when we only need the count.

2. Overthinking the Greedy Strategy

Some might question whether taking the smallest balanced substring at each opportunity truly gives the maximum count, leading to overly complex solutions that try to look ahead or backtrack.

Incorrect Approach:

def balancedStringSplit(self, s: str) -> int:
    # Trying to find "optimal" splits by checking multiple possibilities
    def findMaxSplits(index, current_balance):
        if index == len(s):
            return 0 if current_balance == 0 else float('-inf')
      
        # Complex recursive approach with multiple branches
        # ... unnecessary complexity

Why It's a Problem: The greedy approach is provably optimal here. Any balanced substring can be decomposed into smaller balanced substrings, so taking the smallest ones maximizes the count.

3. Using Wrong Balance Direction

Mixing up which character should increment vs. decrement the counter, leading to incorrect results.

Incorrect Approach:

def balancedStringSplit(self, s: str) -> int:
    balanced_count = 0
    balance = 0
  
    for char in s:
        if char == 'L':
            balance -= 1  # Wrong direction!
        else:
            balance += 1  # Wrong direction!
      
        if balance == 0:
            balanced_count += 1
  
    return balanced_count

Why It's a Problem: While this might work for some inputs due to symmetry, it's conceptually incorrect and makes the code harder to understand. The direction doesn't actually matter as long as it's consistent, but convention and clarity suggest treating one character as positive and the other as negative.

4. Not Handling Edge Cases Properly

Forgetting to consider edge cases like empty strings or single-character strings (though these wouldn't be valid balanced strings according to the problem).

Solution to Avoid Pitfalls:

  • Stick to the simple counter approach without storing substrings
  • Trust the greedy strategy - it's mathematically sound for this problem
  • Be consistent with balance direction (pick one and stick with it)
  • Remember that the problem guarantees a balanced input string, so the counter will always return to 0 by the end
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More