Number of Good Ways to Split a String

MediumHashing · Prefix / Suffix Counts🧩 Hashing & CountingLeetCode #1525 ↗Updated

Problem

Cut the lowercase string s into two non-empty pieces: a left part p and a right part q (so p + q == s). The cut is good when p and q contain the same number of distinct letters. Return how many of the n−1 possible cuts are good.
Inputs = "aacaba"Output2 ("aac|aba" and "aaca|ba")
Inputs = "abcd"Output1 ("ab|cd")
Inputs = "aaaaa"Output4

1 ≤ s.length ≤ 105  ·  s consists of lowercase English letters

Try it yourself

Java
class Solution {
    public int numSplits(String s) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Distinct-count on the left only ever grows as the cut moves right, and on the right it only shrinks. Keep a frequency table for the right side: a letter leaves the right side's distinct set exactly when its count hits 0.
PatternHashing & Counting

Instinct Need "have I seen this before?", "how many times?" or "group things that look alike"? Trade memory for O(1) lookups with a HashMap, HashSet or int[26].

Memory hack Don't search, remember - every element checks in at the front desk, and the desk answers instantly.

Open the Hashing & Counting guide →

Approaches

Brute ForceRecount Both Sides per Cut

Time O(n2)Space O(1)

For each of the n−1 cut positions, scan the left piece and the right piece with a 26-slot boolean table and compare their distinct counts. Each cut costs O(n), so the total is quadratic.

Java
public int numSplits(String s) {
    int n = s.length(), good = 0;
    for (int cut = 1; cut < n; cut++) {
        if (distinct(s, 0, cut) == distinct(s, cut, n)) good++;
    }
    return good;
}

private int distinct(String s, int from, int to) {
    boolean[] seen = new boolean[26];
    int count = 0;
    for (int i = from; i < to; i++) {
        int c = s.charAt(i) - 'a';
        if (!seen[c]) {
            seen[c] = true;
            count++;
        }
    }
    return count;
}

Direct translation of the definition; too slow for n = 105.

At a glance

LevelApproachTimeSpace
Brute ForceRecount Both Sides per CutO(n2)O(1)
BetterPrefix & Suffix Distinct ArraysO(n)O(n)
OptimalOne Sweep with Two Frequency TablesO(n)O(1)
Common pitfalls
  • Allowing an empty left or right part — only cuts 1..n−1 count.
  • Comparing total lengths or letter counts instead of distinct letters.
  • Off-by-one between pre[i] and suf[i+1] in the array version.
Interview tips
  • Point out monotonicity: left distinct never decreases, right distinct never increases.
  • Mention you could binary-search the range of good cuts, but a sweep is simpler.
  • Highlight that the alphabet is fixed (26), so the tables are O(1) space.
Pattern
Prefix / suffix aggregates → when every split point needs a summary of both sides.
My notes saved in this browser