Abstract
Given a string of letters with no spaces or punctuation, how would you figure out how to separate it into words? (Determine if there is a way to separate it into a sequence of valid English words.)
- Category: Dynamic Programming / String Processing
- Input: A string of letters, and (implicitly) a dictionary or oracle that can check whether a given substring is a valid word
- Output: Whether a valid word-split exists, and if so, one such split
- Paradigm: Dynamic Programming
- Typical use cases: tokenization/NLP preprocessing, spell-checking and autocomplete, any “can this sequence be decomposed into valid pieces” problem
Problem Specification
- Instance: A string of letters, with no spaces or punctuation.
- Solution Format: A boolean — does a valid word-split exist — plus, if true, the sequence of break points recovering the actual split.
- Constraints: The substrings between consecutive break points must each individually be a valid dictionary word, and together must cover the entire string with no leftover characters.
- Objective / Goal: Like Selection, this is a decision/existence problem rather than an optimization over many valid solutions — there’s no “better” split to maximize, just “does at least one valid split exist.”
Candidate Strategies / Approaches
Brute Force ✘
Try every possible way of placing word-breaks between the gaps in the string, then check whether every resulting piece is a valid word. There are ways to choose which gaps become breaks — exponential, and (just like Weighted Event Scheduling’s naive backtracking) this recomputes validity checks for the same substrings over and over across different candidate splits.
Dynamic Programming ✔
Same insight as Weighted Event Scheduling: define a small number of genuinely distinct sub-problems — “can the prefix ending at position be validly split?” — and solve them smallest-first, reusing each answer instead of re-deriving it. See The 8 Steps for the general recipe this follows.
Dynamic Programming Solution
1. Define the Array Values (Sub-Problems)
Let be true if can be separated into a sequence of English words, and false otherwise.
2. Base Case
(The empty prefix is vacuously a valid — empty — sequence of words.)
3. Express Recursively
4. Order the Problems
— each only ever depends on some with , so solving in increasing order of guarantees every dependency is already computed.
5. Output
6. Iterative Algorithm
Algorithm 20 String Reconstruction
procedure StringReconstruction()
Initialize all to be False and all to be
for from to do
while not and do
if is true and is a valid word then
= true
else
if then
while do
print()
Fixed an Off-by-One
The source pseudocode’s inner loop condition was
while not S(k) and j > 0, which meansjis decremented down to (but never actually tests)j = 0. That’s a real bug:j = 0is exactly the case “the entire prefix is itself a single valid word,” using the base case — a case that must be checked (e.g. for being the length of the very first word in the string). Changed the condition toj \geq 0above soj=0is actually tested before the loop exits.
Variables & Data Structures
| Name | Type | Purpose |
|---|---|---|
S[] | Boolean array, size | S[k] = whether the prefix has a valid word-split |
prev[] | Array, size | prev[k] = the break point that produced S[k] = True, used to reconstruct the actual split |
j | Index | Candidate previous break point, checked from down to |
Helper Functions / Operations Used
x[j+1...k]is a valid word — a dictionary lookup; if backed by a hash set, though extracting/hashing the substring itself costs .- Reconstruction via
prev— once is known true, walk theprevpointers from back to , printing each break point; at most hops, so .
Proof of Correctness / Optimality
Claim: is set to True if and only if can be validly split into a sequence of words.
- Base case: — the empty prefix trivially has a valid (empty) split.
- Inductive Hypothesis: for all , is set correctly.
- Inductive Step: the algorithm sets exactly when it finds some with (correct, by the Inductive Hypothesis) and a valid word. This matches the recursive definition in Step 3 directly: should be true iff some such exists. Since the (corrected) loop checks every from down to inclusive, it examines every possible split point — so it sets if and only if a valid actually exists.
Why the off-by-one mattered for correctness: without checking , the algorithm would incorrectly conclude for any where the only valid split has the entire prefix as a single word — a real, not just cosmetic, correctness gap.
Time & Space Complexity Analysis
General Case
| Complexity | Notes | |
|---|---|---|
| Time | to | Outer loop runs times; inner while loop checks up to candidate values of ; each check’s word-validity lookup costs with a precomputed hash set (giving total) or if the substring must be extracted/hashed fresh each time (giving worst case) |
| Space | S[] and prev[] are both size |
Best / Worst / Average Case
- Best case: — if every prefix is only ever split at the immediately preceding position ( always works first), each
whileloop exits after one check. - Worst case: as above (– depending on the word-lookup cost) — occurs when many candidate values must be tried before (or without) finding a valid split.
- Average case: depends heavily on the actual dictionary and input string; not meaningfully different from the worst case in general without further assumptions.
Drawbacks / Constraints
- Depends on an unspecified dictionary/oracle. The algorithm assumes “is a valid word” can be checked, but doesn’t specify how the dictionary itself is represented or looked up — this is where the real-world implementation cost hides (see complexity table above).
- Finds one split, not all of them. Since
prev[k]stores only the first (highest) found, the algorithm can’t enumerate every possible decomposition when a string is ambiguous (e.g. a string splittable multiple different ways) — it just proves existence and recovers one witness. - No notion of “best” split. If disambiguating between multiple valid splits matters (e.g. preferring fewer, longer words, or the most common words), this needs to become an optimization variant — assign each word a value/cost and maximize/minimize over valid splits, the same way Weighted Event Scheduling extends plain Event Scheduling.