Number of Ways to Split a String — Medium Problem & Solution
Given a binary string s, split it into three non-empty parts s1 + s2 + s3 so that each part contains the same number of '1' characters.
- Difficulty: Medium
- Topics: Strings, Math, Counting
- Asked at: Amazon, Google, Goldman Sachs
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Given a binary string s, split it into three non-empty parts s1 + s2 + s3 so that each part contains the same number of '1' characters.
Return how many such splits exist, modulo 10^9 + 7.
Example 1
Input: s = "10101"
Output: 4
Explanation: Each part must hold exactly one 1; there are four valid cut placements.
Example 2
Input: s = "1001"
Output: 0
Explanation: Two ones cannot be shared equally among three parts.
Example 3
Input: s = "0000"
Output: 3
Explanation: With no ones at all, any two of the three cut positions work.
Constraints
3 <= s.length <= 100000s[i] is '0' or '1'.
How to solve Number of Ways to Split a String
The positions of the ones fix where the cuts may go. Between the k-th and (k+1)-th one there is a run of zeros, and the cut may land anywhere inside it — the number of choices is that run's length plus one. The two cuts are independent, so the answers multiply.
Approach
- Count the total number of ones. If it is not a multiple of 3, return 0.
- If there are no ones, every pair of distinct cut positions works: return
(n-1)(n-2)/2 mod 1e9+7. - Otherwise let
per = ones / 3. Sweep the string counting ones seen so far; each'0'encountered while exactlyperones have been seen is one extra placement for the first cut, and each'0'at exactly2 * perones is one for the second. - Return
(gap1 + 1) * (gap2 + 1) mod 1e9+7.
Why it works
Every valid split assigns the first per ones to s1, the next per to s2 and the rest to s3. The first cut must sit after the per-th one and before the (per+1)-th, so it has exactly one position per zero in between, plus the position immediately after that one. The same holds for the second cut, and the two ranges are disjoint, so the choices are independent.
Complexity
- Time —
O(n) - Space —
O(1)
Pitfalls
- Forgetting the all-zero case, which the gap formula does not cover.
- Overflow:
(gap1 + 1) * (gap2 + 1)can exceed 32 bits before the modulo, so use a 64-bit intermediate. (n-1)(n-2)/2must be reduced modulo after the division, not before.
Reference solution
Python
def numWays(s: str) -> int:
MOD = 1000000007
n = len(s)
ones = s.count("1")
if ones % 3 != 0:
return 0
if ones == 0:
return ((n - 1) * (n - 2) // 2) % MOD
per = ones // 3
gap1 = gap2 = 0
seen = 0
for c in s:
if c == "1":
seen += 1
else:
if seen == per:
gap1 += 1
if seen == 2 * per:
gap2 += 1
return ((gap1 + 1) * (gap2 + 1)) % MODJavaScript
var numWays = function(s) {
var MOD = 1000000007;
var n = s.length, ones = 0;
for (var i = 0; i < n; i++) {
if (s.charAt(i) === "1") ones++;
}
if (ones % 3 !== 0) return 0;
if (ones === 0) return (((n - 1) * (n - 2)) / 2) % MOD;
var per = ones / 3;
var gap1 = 0, gap2 = 0, seen = 0;
for (var j = 0; j < n; j++) {
if (s.charAt(j) === "1") seen++;
else {
if (seen === per) gap1++;
if (seen === 2 * per) gap2++;
}
}
return ((gap1 + 1) * (gap2 + 1)) % MOD;
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.