Given a string s
consisting only of the characters 'L' and 'R', you are asked to split the string into the maximum number of non-overlapping substrings such that each substring is "balanced". A balanced string is defined as one where the number of 'L' characters is equal to the number of 'R' characters.
You must return the maximum number of balanced substrings you can obtain. Each character from s
must be used exactly once, and substrings must be contiguous. You cannot reuse elements across substrings.
Example:
Input: s = "RLRRLLRLRL"
Output: 4
Explanation: The string can be split into "RL", "RRLL", "RL", "RL", each of which is balanced.
Constraints:
s[i]
is either 'L' or 'R'
The first instinct might be to try all possible ways to split the string and check if the resulting substrings are balanced. However, this brute-force approach would be inefficient, especially as the length of s
grows.
Instead, it's helpful to look for a more efficient way. Since a balanced substring requires an equal number of 'L' and 'R', we can track the "balance" as we iterate through the string. If we use a counter that increases for one character (say, 'L') and decreases for the other ('R'), we can identify points where the counter returns to zero — these are the endpoints of balanced substrings.
This realization leads to a greedy approach: as soon as we find a balanced substring, we split there and continue. This ensures we get the maximum number of balanced substrings.
To solve this problem efficiently, we use a simple counter to track the balance between 'L' and 'R' as we traverse the string. Here is the step-by-step approach:
balance
variable to 0. This will track the difference between the number of 'L' and 'R' characters seen so far.count
variable to 0. This will keep track of the number of balanced substrings found.s
:
balance
by 1.balance
by 1.balance
becomes 0, it means we have found a balanced substring. Increment count
by 1.count
as the answer.This approach is greedy, as it makes a split as soon as a balanced substring is found, which ensures the maximum number of splits.
Let's walk through the example input s = "RLRRLLRLRL"
step by step:
balance = 0
, count = 0
.balance = -1
balance = 0
(Found balanced substring! count = 1
)balance = -1
balance = -2
balance = -1
balance = 0
(Found balanced substring! count = 2
)balance = -1
balance = 0
(Found balanced substring! count = 3
)balance = -1
balance = 0
(Found balanced substring! count = 4
)
At the end, we have count = 4
, which matches the expected output.
Brute-force approach:
Trying all possible splits and checking if each substring is balanced would require examining all possible partitions. There are exponentially many ways to split a string, so the time complexity would be much greater than O(n), potentially O(2n).
Optimized (greedy) approach:
We only need a single pass through the string, updating two variables at each step. So, the time complexity is O(n), where n is the length of the string.
The space complexity is O(1) since we use only a few integer variables, regardless of input size.
The key insight to solving this problem efficiently is to use a running balance counter. By incrementing or decrementing the balance as we encounter 'L' or 'R', we can identify balanced substrings in a single pass. This greedy approach is optimal and elegant, allowing us to maximize the number of balanced splits with minimal computation.
This technique — reducing a problem to a prefix sum or running balance — is a powerful tool in many string and array problems, especially where "balance" or "matching" constraints are involved.
class Solution:
def balancedStringSplit(self, s: str) -> int:
balance = 0
count = 0
for char in s:
if char == 'L':
balance += 1
else: # char == 'R'
balance -= 1
if balance == 0:
count += 1
return count
class Solution {
public:
int balancedStringSplit(string s) {
int balance = 0, count = 0;
for (char c : s) {
if (c == 'L') balance++;
else balance--;
if (balance == 0) count++;
}
return count;
}
};
class Solution {
public int balancedStringSplit(String s) {
int balance = 0, count = 0;
for (char c : s.toCharArray()) {
if (c == 'L') balance++;
else balance--;
if (balance == 0) count++;
}
return count;
}
}
var balancedStringSplit = function(s) {
let balance = 0, count = 0;
for (let c of s) {
if (c === 'L') balance++;
else balance--;
if (balance === 0) count++;
}
return count;
};