Problem. Given a string and a dictionary, can be segmented into a sequence of dictionary words? And: how many ways, or what are they?

The DP โ€” (with lookup)

bool wordBreak(const string& s, const unordered_set<string>& dict) {
    int n = s.size(), maxLen = 0;
    for (auto& w : dict) maxLen = max(maxLen, (int)w.size());
 
    vector<bool> dp(n + 1, false);
    dp[0] = true;
    for (int i = 1; i <= n; i++)
        for (int j = max(0, i - maxLen); j < i; j++)
            if (dp[j] && dict.count(s.substr(j, i - j))) { dp[i] = true; break; }
    return dp[n];
}

Bounding the inner loop by the longest dictionary word is the essential optimisation: substring lookups instead of .

With a trie โ€” no substring construction

s.substr allocates a string per check, which dominates the runtime. A trie walk avoids it entirely:

for (int i = 0; i < n; i++) {
    if (!dp[i]) continue;
    int v = 0;
    for (int j = i; j < n; j++) {
        v = trie.nxt[v][s[j] - 'a'];
        if (v == -1) break;                          // no dictionary word continues
        if (trie.isEnd[v]) dp[j + 1] = true;
    }
}

worst case, but the early break makes it near-linear on real dictionaries. This is the version to write.

The variants

VariantChange
Can it be segmented?boolean DP
Count the segmentationsdp[i] += dp[j] โ€” beware of exponential counts, take mod
Output all segmentationsDFS with memoization; the output can be exponential
Output one segmentationstore a from[i] pointer and walk back
Minimum number of wordsdp[i] = min(dp[j] + 1)
Maximum total word valuedp[i] = max(dp[j] + value)
Allow at most unknown charactersadd a dimension
Dictionary given as a triethe walk above
Words may be reusedalready allowed
Each word at most onceNP-hard in general

Enumerating all segmentations

The number of segmentations can be exponential ("aaaa...a" with dictionary {a, aa}). Memoised DFS returning a list of strings will blow up on such inputs. Count them with a DP instead, and only enumerate when the problem bounds the output.

Aho-Corasick for many patterns

When the dictionary is large, build an Aho-Corasick automaton and run through it once. At each position, the suffix-link chain gives every dictionary word ending there:

int state = 0;
for (int i = 0; i < n; i++) {
    state = aut[state][s[i] - 'a'];
    for (int v = state; v; v = wordLink[v])          // words ending at i
        dp[i + 1] = dp[i + 1] || dp[i + 1 - len[v]];
}

โ€” the right approach when the dictionary has words.

ProblemRecurrence shape
Word break from all valid
Palindrome partitioning (min cuts) over palindromic
Decode ways (digits to letters) from and
Concatenated wordsword break with a length constraint
Text justification โ€” Monge, so Knuth applies
Segment into partsadd a dimension

Text justification (Knuth-Plass line breaking) is the same shape with a convex cost, which is why SMAWK and D&C DP apply to it โ€” the classical example of those optimisations.

Why it is worth knowing

It is the canonical โ€œprefix DP over a string with a validity predicateโ€. Once you recognise that shape โ€” dp[i] depends on all dp[j] where s[j..i) is valid โ€” a large family of segmentation, partitioning and parsing problems becomes routine, and the only remaining question is how fast you can test the predicate.

See also: String DP ยท Trie ยท Aho-Corasick