Number of Ways to Split a String
MedPrompt
Given a binary string s, you can split s into 3 non-empty strings s1, s2, and s3 where s1 + s2 + s3 = s.
Return the number of ways s can be split such that the number of ones is the same in s1, s2, and s3. Since the answer may be too large, return it modulo 109 + 7.
Example 1:
Input: s = "10101"
Output: 4
Explanation: There are four ways to split s in 3 parts where each part contain the same number of letters '1'.
"1|010|1"
"1|01|01"
"10|10|1"
"10|1|01"Example 2:
Input: s = "1001"
Output: 0Example 3:
Input: s = "0000"
Output: 3
Explanation: There are three ways to split s in 3 parts.
"0|0|00"
"0|00|0"
"00|0|0"
Constraints:
3 <= s.length <= 105s[i]is either'0'or'1'.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach exhaustively checks every possible way to split the string into three non-empty parts. It uses nested loops to define the two cut points and then, for each split, counts the number of '1's in each of the three resulting substrings to see if they are equal.
Algorithm
- Initialize a counter
waysto 0. - Use a nested loop structure to iterate through all possible pairs of cut points. The outer loop for the first cut
iruns from0ton-3, and the inner loop for the second cutjruns fromi+1ton-2. - For each pair
(i, j), split the stringsinto three substrings:s1 = s.substring(0, i + 1),s2 = s.substring(i + 1, j + 1), ands3 = s.substring(j + 1). - Create a helper function
countOnesto count the number of '1's in a string. - Call
countOnesfors1,s2, ands3. - If the counts are equal, increment the
wayscounter. - After the loops complete, return
ways.
Walkthrough
The algorithm iterates through all possible pairs of cut points (i, j). The first cut is after index i and the second after index j. The loops ensure that 0 <= i < j < n-1, which guarantees three non-empty substrings: s1 = s[0...i], s2 = s[i+1...j], and s3 = s[j+1...n-1]. For each such split, we manually count the '1's in s1, s2, and s3. If the counts are equal, we increment a result counter. This method is simple to understand but highly inefficient due to the repeated counting within the nested loops.
class Solution { public int numWays(String s) { int n = s.length(); long ways = 0; int MOD = 1_000_000_007; // i is the end index of s1 for (int i = 0; i < n - 2; i++) { // j is the end index of s2 for (int j = i + 1; j < n - 1; j++) { String s1 = s.substring(0, i + 1); String s2 = s.substring(i + 1, j + 1); String s3 = s.substring(j + 1); int ones1 = countOnes(s1); int ones2 = countOnes(s2); int ones3 = countOnes(s3); if (ones1 == ones2 && ones2 == ones3) { ways++; } } } return (int) (ways % MOD); } private int countOnes(String str) { int count = 0; for (char c : str.toCharArray()) { if (c == '1') { count++; } } return count; }}Complexity
Time
O(N^3), where N is the length of the string. The two nested loops run in O(N^2), and inside the loops, creating and counting ones in substrings takes O(N) time.
Space
O(N), where N is the length of the string. This is due to the creation of substrings in each iteration.
Trade-offs
Pros
Simple to conceptualize and implement.
Cons
Extremely inefficient due to its O(N^3) time complexity.
Will result in a 'Time Limit Exceeded' (TLE) error for the given constraints.
This approach improves upon the brute-force method by pre-calculating the number of '1's up to each index using a prefix sum array. This allows for O(1) lookup of the number of '1's in any substring, reducing the overall complexity from cubic to quadratic.
Algorithm
- Create a prefix sum array
prefixOnesof sizen+1. - Iterate through the input string
sfromi = 0ton-1, populatingprefixOnes[i+1]withprefixOnes[i] + (s.charAt(i) == '1' ? 1 : 0). - Initialize a counter
waysto 0. - Use the same nested loop structure as the brute-force approach to iterate through all cut points
(i, j). - Inside the loops, calculate the number of '1's in each part in O(1) time using the
prefixOnesarray:ones1 = prefixOnes[i+1]ones2 = prefixOnes[j+1] - prefixOnes[i+1]ones3 = prefixOnes[n] - prefixOnes[j+1]
- If
ones1,ones2, andones3are equal, incrementways. - Return
waysafter the loops.
Walkthrough
We first create a prefix sum array, prefixOnes, where prefixOnes[i] stores the total count of '1's in the substring s[0...i-1]. This array can be built in a single pass (O(N)). After this pre-computation, we use the same nested loop structure as the brute-force approach to iterate through all possible split points (i, j). However, instead of re-counting '1's for each substring, we use the prefixOnes array to find the counts in O(1) time. This optimization significantly improves performance, but it's not sufficient for the problem's constraints.
class Solution { public int numWays(String s) { int n = s.length(); int MOD = 1_000_000_007; int[] prefixOnes = new int[n + 1]; for (int i = 0; i < n; i++) { prefixOnes[i + 1] = prefixOnes[i] + (s.charAt(i) - '0'); } long ways = 0; int totalOnes = prefixOnes[n]; // i is the end index of s1 for (int i = 0; i < n - 2; i++) { // j is the end index of s2 for (int j = i + 1; j < n - 1; j++) { int ones1 = prefixOnes[i + 1]; int ones2 = prefixOnes[j + 1] - prefixOnes[i + 1]; // ones3 can be calculated without another lookup int ones3 = totalOnes - prefixOnes[j + 1]; if (ones1 == ones2 && ones2 == ones3) { ways++; } } } return (int) (ways % MOD); }}Complexity
Time
O(N^2). The pre-computation of prefix sums takes O(N), but the dominant part is the nested loops which run in O(N^2).
Space
O(N) to store the prefix sum array.
Trade-offs
Pros
Faster than the naive brute-force approach.
Introduces a common optimization technique (prefix sums).
Cons
Still too slow for the given constraints. An O(N^2) solution will time out when N is up to 10^5.
This is the most efficient approach, solving the problem in linear time. It relies on a key insight: if the total number of '1's is C, each of the three parts must have C/3 ones. The problem then reduces to a combinatorial task of finding how many ways we can place the two cuts to satisfy this condition.
Algorithm
- First, iterate through the string to count the total number of '1's,
totalOnes. - If
totalOnesis not divisible by 3, return 0, as an equal split is impossible. - Case 1:
totalOnes == 0. The string consists only of '0's. Any split is valid. We need to choose 2 cut positions fromn-1available gaps. The number of ways isC(n-1, 2) = (n-1) * (n-2) / 2. Calculate this value modulo10^9 + 7. - Case 2:
totalOnes > 0. Letk = totalOnes / 3. We need to find the number of ways to place the first cut and the second cut.- The first cut can be placed anywhere between the
k-th '1' and the(k+1)-th '1'. - The second cut can be placed anywhere between the
2k-th '1' and the(2k+1)-th '1'.
- The first cut can be placed anywhere between the
- Initialize
ways1 = 0,ways2 = 0, andcurrentOnes = 0. - Iterate through the string one more time. If
s[i] == '1', incrementcurrentOnes. - If
currentOnes == k, we are in a valid region for the first cut. Incrementways1. - If
currentOnes == 2k, we are in a valid region for the second cut. Incrementways2. - The total number of ways is
(ways1 * ways2). Return this product modulo10^9 + 7. Uselongfor the multiplication to prevent overflow.
Walkthrough
- First, we count the total number of '1's in the string,
totalOnes. - A valid split is only possible if
totalOnesis a multiple of 3. If not, we immediately return 0. - We handle the special case where
totalOnesis 0. If the string contains no '1's, any split into three non-empty parts is valid. The problem becomes choosing 2 cut positions from then-1available gaps between characters. The number of ways is given by the combination formulaC(n-1, 2) = (n-1) * (n-2) / 2. - For the general case (
totalOnes > 0), letk = totalOnes / 3. The first parts1must containkones, and the second parts2must also containkones. This means the first cut must be placed after thek-th '1' but before the(k+1)-th '1'. The number of possible positions for the first cut (ways1) is the number of zeros between thek-th and(k+1)-th '1's, plus one. Similarly, the number of positions for the second cut (ways2) is determined by the zeros between the2k-th and(2k+1)-th '1's. We can findways1andways2in a single pass by counting the number of indices where the prefix of the string contains exactlykones and2kones, respectively.
class Solution { public int numWays(String s) { int n = s.length(); int MOD = 1_000_000_007; int totalOnes = 0; for (char c : s.toCharArray()) { if (c == '1') { totalOnes++; } } if (totalOnes % 3 != 0) { return 0; } if (totalOnes == 0) { // Choose 2 cut positions from n-1 gaps: C(n-1, 2) long ways = (long)(n - 1) * (n - 2) / 2; return (int)(ways % MOD); } int onesPerPart = totalOnes / 3; long ways1 = 0; long ways2 = 0; int currentOnes = 0; for (int i = 0; i < n; i++) { if (s.charAt(i) == '1') { currentOnes++; } // Count valid positions for the first cut if (currentOnes == onesPerPart) { ways1++; } // Count valid positions for the second cut else if (currentOnes == 2 * onesPerPart) { ways2++; } } long result = (ways1 * ways2) % MOD; return (int) result; }}Complexity
Time
O(N), as we iterate through the string a constant number of times (at most two passes).
Space
O(1), as we only use a few variables to store counts, regardless of the input size.
Trade-offs
Pros
Highly efficient with linear time complexity, passing all constraints.
Low constant space usage.
Cons
The logic is more complex than brute-force, requiring careful handling of edge cases like the all-zeros string.
Solutions
Solution
class Solution {private String s;public int numWays(String s) { this.s = s; int cnt = 0; int n = s.length(); for (int i = 0; i < n; ++i) { if (s.charAt(i) == '1') { ++cnt; } } int m = cnt % 3; if (m != 0) { return 0; } final int mod = (int)1 e9 + 7; if (cnt == 0) { return (int)(((n - 1L) * (n - 2) / 2) % mod); } cnt /= 3; long i1 = find(cnt), i2 = find(cnt + 1); long j1 = find(cnt * 2), j2 = find(cnt * 2 + 1); return (int)((i2 - i1) * (j2 - j1) % mod); }private int find(int x) { int t = 0; for (int i = 0;; ++i) { t += s.charAt(i) == '1' ? 1 : 0; if (t == x) { return i; } } }}class Solution {public: int numWays(string s) { int cnt = 0; for (char &c : s) { cnt += c == '1'; } int m = cnt % 3; if (m) { return 0; } const int mod = 1e9 + 7; int n = s.size(); if (cnt == 0) { return (n - 1LL) * (n - 2) / 2 % mod; } cnt /= 3; auto find = [&](int x) { int t = 0; for (int i = 0;; ++i) { t += s[i] == '1'; if (t == x) { return i; } } }; int i1 = find(cnt), i2 = find(cnt + 1); int j1 = find(cnt * 2), j2 = find(cnt * 2 + 1); return (1LL * (i2 - i1) * (j2 - j1)) % mod; }};class Solution: def numWays(self, s: str) -> int: def find(x): t = 0 for i, c in enumerate(s): t += int(c == '1') if t == x: return i cnt, m = divmod(sum(c == '1' for c in s), 3) if m: return 0 n = len(s) mod = 10 ** 9 + 7 if cnt == 0: return ((n - 1) * (n - 2) // 2) % mod i1, i2 = find(cnt), find(cnt + 1) j1, j2 = find(cnt * 2), find(cnt * 2 + 1) return (i2 - i1) * (j2 - j1) % (10 ** 9 + 7)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.