1422. Maximum Score After Splitting a String
Problem Description
You are given a string s
consisting only of zeros ('0') and ones ('1'). Your task is to split this string into two non-empty parts (a left substring and a right substring) and calculate the maximum possible score.
The score is calculated as follows:
- Count the number of zeros in the left substring
- Count the number of ones in the right substring
- Add these two counts together to get the score
You need to find the maximum score possible among all valid ways to split the string.
For example, if s = "011101"
:
- If you split after the first character: left = "0", right = "11101"
- Score = 1 zero in left + 4 ones in right = 5
- If you split after the second character: left = "01", right = "1101"
- Score = 1 zero in left + 3 ones in right = 4
- If you split after the third character: left = "011", right = "101"
- Score = 1 zero in left + 2 ones in right = 3
The constraint is that both parts must be non-empty, meaning you cannot take the entire string as left (leaving right empty) or take the entire string as right (leaving left empty).
Your goal is to return the maximum score achievable.
Intuition
The key insight is that we need to try every possible split position and find which one gives us the maximum score. Since we're splitting the string into two parts, we have n-1
possible positions to split (where n
is the length of the string) - we can split after the 1st character, 2nd character, and so on, up to the second-to-last character.
A naive approach would be to actually split the string at each position and count zeros and ones in each part, but this would be inefficient. Instead, we can think about what happens as we move the split position from left to right:
- Initially, if we split after the first character, we have one character on the left and all remaining characters on the right
- As we move the split position one step to the right, we're essentially moving one character from the right substring to the left substring
This movement has a predictable effect on our score:
- If the character we're moving is a '0', it increases the left count (good for score) and doesn't affect the right count of ones
- If the character we're moving is a '1', it doesn't help the left count but decreases the right count of ones (bad for score)
So we can start by counting all the '1's in the string (this would be our right count if we split after the first character with an empty left count initially). Then, as we iterate through each potential split position:
- For each '0' we encounter, we increment our left count
l
by 1 - For each '1' we encounter, we decrement our right count
r
by 1 - At each position, we calculate the score as
l + r
and track the maximum
The expression int(x) ^ 1
is a clever way to count zeros: it converts '0' to 0 and '1' to 1, then XOR with 1 flips the bit (0 becomes 1, 1 becomes 0), effectively counting zeros instead of ones.
Learn more about Prefix Sum patterns.
Solution Approach
The solution implements a single-pass counting approach to find the maximum score efficiently.
Initialization:
l = 0
: Tracks the count of zeros in the left substring (initially empty)r = s.count("1")
: Counts all ones in the string (initially all characters are in the right substring)ans = 0
: Stores the maximum score found so far
Main Algorithm:
We iterate through the string from index 0 to n-2
(using s[:-1]
to exclude the last character). This ensures both substrings remain non-empty after the split.
For each character x
at position i
:
-
Update left count:
l += int(x) ^ 1
int(x)
converts '0' to 0 and '1' to 1- XOR with 1 (
^ 1
) flips the bit: 0 becomes 1, 1 becomes 0 - This effectively adds 1 to
l
if the character is '0', and adds 0 if it's '1'
-
Update right count:
r -= int(x)
- Subtracts 1 from
r
if the character is '1' - Subtracts 0 from
r
if the character is '0' - This removes the ones that have moved from right to left substring
- Subtracts 1 from
-
Update maximum score:
ans = max(ans, l + r)
- Calculate the current score as the sum of zeros in left and ones in right
- Keep track of the maximum score seen so far
Why this works:
At each iteration, we're conceptually moving the split position one character to the right. The character at position i
moves from the right substring to the left substring. The counts are adjusted accordingly, and we check if this new split gives us a better score.
Time Complexity: O(n)
where n is the length of the string - we make one initial pass to count ones and one main pass through the string.
Space Complexity: O(1)
- we only use a constant amount of extra space for variables.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "00111"
:
Initial Setup:
l = 0
(no zeros counted in left substring yet)r = s.count("1") = 3
(all three ones are initially in the "right" side)ans = 0
(maximum score so far)
Iteration 1: Character at index 0 is '0'
- Split would be: left = "0" | right = "0111"
l += int('0') ^ 1 = 0 ^ 1 = 1
, sol = 1
r -= int('0') = 0
, sor = 3
(no ones moved out)- Current score =
l + r = 1 + 3 = 4
ans = max(0, 4) = 4
Iteration 2: Character at index 1 is '0'
- Split would be: left = "00" | right = "111"
l += int('0') ^ 1 = 0 ^ 1 = 1
, sol = 2
r -= int('0') = 0
, sor = 3
(no ones moved out)- Current score =
l + r = 2 + 3 = 5
ans = max(4, 5) = 5
Iteration 3: Character at index 2 is '1'
- Split would be: left = "001" | right = "11"
l += int('1') ^ 1 = 1 ^ 1 = 0
, sol = 2
(no change, not a zero)r -= int('1') = 1
, sor = 2
(one '1' moved to left side)- Current score =
l + r = 2 + 2 = 4
ans = max(5, 4) = 5
Iteration 4: Character at index 3 is '1'
- Split would be: left = "0011" | right = "1"
l += int('1') ^ 1 = 1 ^ 1 = 0
, sol = 2
(no change, not a zero)r -= int('1') = 1
, sor = 1
(another '1' moved to left side)- Current score =
l + r = 2 + 1 = 3
ans = max(5, 3) = 5
Result: The maximum score is 5, achieved when splitting after the second character ("00" | "111").
Solution Implementation
1class Solution:
2 def maxScore(self, s: str) -> int:
3 # Initialize counters for left and right parts
4 # left_zeros: count of zeros in the left substring (starts at 0)
5 # right_ones: count of ones in the right substring (initially all ones)
6 left_zeros = 0
7 right_ones = s.count("1")
8
9 # Track the maximum score found
10 max_score = 0
11
12 # Iterate through all possible split positions (except the last character
13 # to ensure right substring is non-empty)
14 for i in range(len(s) - 1):
15 char = s[i]
16
17 # Update counters based on current character
18 if char == "0":
19 # Add a zero to the left part
20 left_zeros += 1
21 else:
22 # Move a one from right part to left part
23 right_ones -= 1
24
25 # Calculate current score and update maximum
26 current_score = left_zeros + right_ones
27 max_score = max(max_score, current_score)
28
29 return max_score
30
1class Solution {
2 public int maxScore(String s) {
3 // Initialize counters for left substring (zeros) and right substring (ones)
4 int leftZeros = 0;
5 int rightOnes = 0;
6 int length = s.length();
7
8 // Count total number of ones in the entire string
9 // This will be the initial count for the right substring
10 for (int i = 0; i < length; ++i) {
11 if (s.charAt(i) == '1') {
12 ++rightOnes;
13 }
14 }
15
16 // Variable to store the maximum score
17 int maxScore = 0;
18
19 // Iterate through possible split positions (cannot split at the last position)
20 // Split position i means: left substring [0, i], right substring [i+1, n-1]
21 for (int i = 0; i < length - 1; ++i) {
22 // Update left zeros count
23 // If current character is '0', increment leftZeros
24 // (s.charAt(i) - '0') ^ 1 converts '0' to 1 and '1' to 0
25 leftZeros += (s.charAt(i) - '0') ^ 1;
26
27 // Update right ones count
28 // If current character is '1', decrement rightOnes
29 // since this '1' is now part of the left substring
30 rightOnes -= s.charAt(i) - '0';
31
32 // Calculate current score and update maximum
33 maxScore = Math.max(maxScore, leftZeros + rightOnes);
34 }
35
36 return maxScore;
37 }
38}
39
1class Solution {
2public:
3 int maxScore(string s) {
4 // Count zeros in left substring (initially empty)
5 int leftZeros = 0;
6
7 // Count all ones in the string (initially all in right substring)
8 int rightOnes = count(s.begin(), s.end(), '1');
9
10 // Track maximum score
11 int maxScore = 0;
12
13 // Iterate through possible split positions (exclude last position)
14 // We stop at s.size() - 1 because both substrings must be non-empty
15 for (int i = 0; i < s.size() - 1; ++i) {
16 // Update left zeros count
17 // If s[i] is '0', increment leftZeros (XOR with 1 flips 0 to 1)
18 leftZeros += (s[i] - '0') ^ 1;
19
20 // Update right ones count
21 // If s[i] is '1', decrement rightOnes (moving it from right to left)
22 rightOnes -= s[i] - '0';
23
24 // Calculate current score and update maximum
25 maxScore = max(maxScore, leftZeros + rightOnes);
26 }
27
28 return maxScore;
29 }
30};
31
1/**
2 * Calculates the maximum score by splitting a binary string into two non-empty substrings.
3 * Score = number of '0's in left substring + number of '1's in right substring
4 * @param s - Binary string containing only '0' and '1' characters
5 * @returns Maximum possible score
6 */
7function maxScore(s: string): number {
8 // Initialize counters for zeros in left part and ones in right part
9 let zerosInLeft: number = 0;
10 let onesInRight: number = 0;
11
12 // Count total number of '1's in the entire string (initially all in right part)
13 for (const char of s) {
14 if (char === '1') {
15 onesInRight++;
16 }
17 }
18
19 let maxScoreValue: number = 0;
20
21 // Iterate through possible split positions (cannot split at the last position)
22 for (let i = 0; i < s.length - 1; i++) {
23 // Update counters based on current character moving from right to left part
24 if (s[i] === '0') {
25 // Found a '0', increase left counter
26 zerosInLeft++;
27 } else {
28 // Found a '1', decrease right counter as it moves to left part
29 onesInRight--;
30 }
31
32 // Calculate current score and update maximum
33 const currentScore: number = zerosInLeft + onesInRight;
34 maxScoreValue = Math.max(maxScoreValue, currentScore);
35 }
36
37 return maxScoreValue;
38}
39
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. The algorithm performs the following operations:
s.count("1")
traverses the entire string once:O(n)
- The for loop iterates through
n-1
characters of the string:O(n-1) = O(n)
- Each operation inside the loop (XOR, addition, subtraction, max comparison) takes constant time:
O(1)
Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
The space complexity is O(1)
. The algorithm only uses a fixed number of variables (l
, r
, ans
, and x
) regardless of the input size, requiring constant extra space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Edge Cases with String Boundaries
A critical pitfall is iterating through the entire string instead of stopping at len(s) - 1
. This would result in considering a split where the right substring is empty, which violates the problem constraint.
Incorrect Implementation:
# WRONG: This iterates through all characters including the last one
for i in range(len(s)): # Bug: includes index len(s)-1
char = s[i]
# ... rest of the logic
Why it's wrong: When i = len(s) - 1
, the split would be:
- Left substring: entire string
s
- Right substring: empty string ""
This violates the "non-empty" constraint for both parts.
Correct Implementation:
# CORRECT: Stop before the last character
for i in range(len(s) - 1): # Ensures right substring is never empty
char = s[i]
# ... rest of the logic
2. Incorrect Initial Score Calculation
Another common mistake is initializing max_score
with the wrong value or forgetting that we need to actually check splits, not just initialize with a theoretical value.
Incorrect Implementation:
# WRONG: Assuming the initial state is a valid split left_zeros = 0 right_ones = s.count("1") max_score = left_zeros + right_ones # Bug: This represents no split at all
Why it's wrong: The initial values left_zeros = 0
and right_ones = s.count("1")
don't represent any actual split of the string - they represent a state where nothing is in the left substring.
Correct Implementation:
# CORRECT: Initialize max_score to 0 and let the loop find the actual maximum max_score = 0 # The first iteration will set a valid initial score
3. Off-by-One Error in Character Movement Logic
A subtle pitfall is incorrectly updating the counters when moving a character from right to left.
Incorrect Implementation:
# WRONG: Decrementing right_ones for zeros too
for i in range(len(s) - 1):
char = s[i]
if char == "0":
left_zeros += 1
right_ones -= 1 # Bug: zeros don't affect right_ones count
else:
right_ones -= 1
Correct Implementation:
# CORRECT: Only decrement right_ones when moving a '1' from right to left
for i in range(len(s) - 1):
char = s[i]
if char == "0":
left_zeros += 1
# No change to right_ones (zeros don't count in right substring)
else:
right_ones -= 1 # Only decrease when moving a '1'
Which of the following is a min heap?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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 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
Want a Structured Path to Master System Design Too? Don’t Miss This!