Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1573. Number of Ways to Split a String - Leetcode Solution

Code Implementation

class Solution:
    def numWays(self, s: str) -> int:
        MOD = 10**9 + 7
        total_ones = s.count('1')
        n = len(s)
        if total_ones == 0:
            # All zeros: choose any 2 cuts from n-1 positions
            return ((n - 1) * (n - 2) // 2) % MOD
        if total_ones % 3 != 0:
            return 0
        ones_per_part = total_ones // 3
        first, second = -1, -1
        ones_count = 0
        # Find the number of zeros after first and second cuts
        first_cut_zeros = second_cut_zeros = 0
        for i, ch in enumerate(s):
            if ch == '1':
                ones_count += 1
            if ones_count == ones_per_part and first == -1:
                first = i
            if ones_count == ones_per_part + 1 and second == -1:
                first_cut_zeros = i - first - 1
                second = i
            if ones_count == 2 * ones_per_part and second_cut_zeros == 0 and i > second:
                second_cut_zeros = i - second - 1
        return ((first_cut_zeros + 1) * (second_cut_zeros + 1)) % MOD
      
class Solution {
public:
    int numWays(string s) {
        const int MOD = 1e9 + 7;
        int n = s.size();
        int totalOnes = 0;
        for(char c : s) if(c == '1') totalOnes++;
        if(totalOnes == 0) {
            // All zeros: choose any 2 cuts from n-1 positions
            long long cuts = (long long)(n - 1) * (n - 2) / 2;
            return (int)(cuts % MOD);
        }
        if(totalOnes % 3 != 0) return 0;
        int onesPerPart = totalOnes / 3;
        int onesCount = 0, first = -1, second = -1;
        long long firstCutZeros = 0, secondCutZeros = 0;
        for(int i = 0; i < n; ++i) {
            if(s[i] == '1') onesCount++;
            if(onesCount == onesPerPart && first == -1) first = i;
            if(onesCount == onesPerPart + 1 && second == -1) {
                firstCutZeros = i - first - 1;
                second = i;
            }
            if(onesCount == 2 * onesPerPart && secondCutZeros == 0 && i > second) {
                secondCutZeros = i - second - 1;
            }
        }
        return (int)(((firstCutZeros + 1) * (secondCutZeros + 1)) % MOD);
    }
};
      
class Solution {
    public int numWays(String s) {
        int MOD = 1000000007;
        int n = s.length();
        int totalOnes = 0;
        for (char c : s.toCharArray()) if (c == '1') totalOnes++;
        if (totalOnes == 0) {
            long cuts = (long)(n - 1) * (n - 2) / 2;
            return (int)(cuts % MOD);
        }
        if (totalOnes % 3 != 0) return 0;
        int onesPerPart = totalOnes / 3;
        int onesCount = 0, first = -1, second = -1;
        long firstCutZeros = 0, secondCutZeros = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '1') onesCount++;
            if (onesCount == onesPerPart && first == -1) first = i;
            if (onesCount == onesPerPart + 1 && second == -1) {
                firstCutZeros = i - first - 1;
                second = i;
            }
            if (onesCount == 2 * onesPerPart && secondCutZeros == 0 && i > second) {
                secondCutZeros = i - second - 1;
            }
        }
        return (int)(((firstCutZeros + 1) * (secondCutZeros + 1)) % MOD);
    }
}
      
var numWays = function(s) {
    const MOD = 1e9 + 7;
    const n = s.length;
    let totalOnes = 0;
    for (let ch of s) if (ch === '1') totalOnes++;
    if (totalOnes === 0) {
        return ((n - 1) * (n - 2) / 2) % MOD;
    }
    if (totalOnes % 3 !== 0) return 0;
    let onesPerPart = totalOnes / 3;
    let onesCount = 0, first = -1, second = -1;
    let firstCutZeros = 0, secondCutZeros = 0;
    for (let i = 0; i < n; i++) {
        if (s[i] === '1') onesCount++;
        if (onesCount === onesPerPart && first === -1) first = i;
        if (onesCount === onesPerPart + 1 && second === -1) {
            firstCutZeros = i - first - 1;
            second = i;
        }
        if (onesCount === 2 * onesPerPart && secondCutZeros === 0 && i > second) {
            secondCutZeros = i - second - 1;
        }
    }
    return ((firstCutZeros + 1) * (secondCutZeros + 1)) % MOD;
};
      

Problem Description

Given a binary string s, you are asked to split it into three non-empty parts such that each part contains the same number of '1's. Return the number of ways you can split s into these three parts. Since the answer may be very large, return it modulo 10^9 + 7.

  • Each part must be non-empty.
  • The split points must be different (no overlap).
  • All splits should use the original order of s (no reordering).
  • If it is not possible to split s as described, return 0.

For example, given s = "10101", the answer is 4.

Thought Process

The first instinct might be to try all possible ways to split the string at two different points and check if each part has the same number of '1's. However, this brute-force approach is inefficient for large strings.

We notice that the only thing that matters is the count of '1's in each part. If the total number of '1's in s is not divisible by 3, it's impossible to split. If there are no '1's at all, then every split is valid as long as the parts are non-empty.

For the general case, we need to find positions to cut so that each part has exactly one-third of the total '1's. The number of ways to split is determined by the number of zeros between these cut points, because zeros do not affect the count of '1's.

Solution Approach

  • Step 1: Count the total number of '1's in s. Let this be totalOnes.
  • Step 2:
    • If totalOnes is 0, then the string is all zeros. The number of ways to split is the number of ways to choose 2 cut points from n-1 possible cut positions: ((n-1) * (n-2)) / 2.
    • If totalOnes % 3 != 0, return 0. It's impossible to split evenly.
  • Step 3: Otherwise, let onesPerPart = totalOnes / 3.
  • Step 4: Traverse the string. Find the number of zeros between:
    • The end of the first part (after the onesPerPart-th '1') and the start of the next '1' (this is the number of possible first cuts).
    • The end of the second part (after the 2 * onesPerPart-th '1') and the start of the next '1' (number of possible second cuts).
  • Step 5: The total number of ways is the product of the number of possible first and second cuts, modulo 10^9 + 7.

This approach avoids brute-force and leverages the properties of the string to achieve an efficient solution.

Example Walkthrough

Let's use s = "10101" as an example.

  • Count total '1's: There are 3.
  • Each part must have 1 '1'.
  • The '1's are at positions 0, 2, and 4.
  • The possible first cut is after the first '1' (position 0), which can be after position 0 or after position 1 (since position 1 is a '0').
  • The possible second cut is after the second '1' (position 2), which can be after position 2 or after position 3 (since position 3 is a '0').
  • So, there are 2 choices for each cut, making 2 * 2 = 4 ways to split.
  • The valid splits are:
    1. 1 | 0 | 101
    2. 1 | 01 | 01
    3. 10 | 1 | 01
    4. 10 | 10 | 1

Time and Space Complexity

  • Brute-force Approach: Checking all possible pairs of cuts is O(n2), which is too slow for large n.
  • Optimized Approach: We traverse the string a few times (to count '1's and to find cut positions), so the time complexity is O(n).
  • Space complexity is O(1) because we only use a few counters.

Summary

The key insight is that the solution depends on counting '1's and zeros, not on the actual arrangement of the string. By focusing on the zeros between the required '1's, we can efficiently compute the number of valid splits without brute-force. This approach is both efficient and elegant, leveraging properties of combinatorics and string processing.