String backtracking
Backtracking on strings: generating balanced parentheses, palindrome partitioning, phone-number letter combinations, restoring IP addresses and enumerating word-break sentences.
Learning goals
- Build a string with StringBuilder and undo by deleting the last character
- Cut a string into pieces that each satisfy a rule
- Prune with early rules such as palindrome, range and dictionary tests
Prerequisites
Generate parentheses
List all balanced strings of n pairs of parentheses. Build them one character at a time with two counters, open and close. You may add ( while open < n, and ) while close < open. These two rules never create an invalid prefix, so every string of length 2n is balanced. There are Catalan(n) results: 1, 2, 5, 14 for n = 1 to 4.
The n = 3 preset finds 5 strings in a tree of 35 nodes; n = 4 finds 14 in 101 nodes. ✗ nodes are options rejected by the two rules.
1static List<String> generateParentheses(int n) {2List<String> out = new ArrayList<>();3gen(0, 0, n, new StringBuilder(), out);4return out;5}6private static void gen(int open, int close, int n, StringBuilder sb, List<String> out) {7if (sb.length() == 2 * n) { out.add(sb.toString()); return; }8if (open < n) { sb.append('('); gen(open + 1, close, n, sb, out); sb.deleteCharAt(sb.length() - 1); }9if (close < open) { sb.append(')'); gen(open, close + 1, n, sb, out); sb.deleteCharAt(sb.length() - 1); }10}
Generate valid parentheses
Interactive · edit the inputpartial solution
solutions found (0)
Build strings of 3 pairs one character at a time. Two validity rules prune the tree: never use more than 3 "(", and never write ")" unless an earlier "(" is still open. Every string that survives is balanced.
1static void gen(int open, int close, int n, StringBuilder sb, List<String> out) {2if (sb.length() == 2 * n) { out.add(sb.toString()); return; }3if (open < n) { // an opening parenthesis is still available4sb.append('('); // choose5gen(open + 1, close, n, sb, out); // explore6sb.deleteCharAt(sb.length() - 1); // unchoose7}8if (close < open) { // a closing one is valid only if something is open9sb.append(')');10gen(open, close + 1, n, sb, out);11sb.deleteCharAt(sb.length() - 1);12}13}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 84 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Build strings of 3 pairs one character at a time. Two validity rules prune the tree: never use more than 3 "(", and never write ")" unless an earlier "(" is still open. Every string that survives is balanced. |
| 2 | L1 | depth=1 solutions=0 | Explore "". |
| 3 | L4 | depth=1 solutions=0 | Choose (. |
| 4 | L1 | depth=2 solutions=0 | Explore "(". |
| 5 | L4 | depth=2 solutions=0 | Choose (. |
| 6 | L1 | depth=3 solutions=0 | Explore "((". |
| 7 | L4 | depth=3 solutions=0 | Choose (. |
| 8 | L1 | depth=4 solutions=0 | Explore "(((". |
| 9 | L3 | depth=5 solutions=0 | Reject (: all 3 opening parentheses are already used. |
| 10 | L9 | depth=4 solutions=0 | Choose ). |
| 11 | L1 | depth=5 solutions=0 | Explore "((()". |
| 12 | L3 | depth=6 solutions=0 | Reject (: all 3 opening parentheses are already used. |
| 13 | L9 | depth=5 solutions=0 | Choose ). |
| 14 | L1 | depth=6 solutions=0 | Explore "((())". |
| 15 | L3 | depth=7 solutions=0 | Reject (: all 3 opening parentheses are already used. |
| 16 | L9 | depth=6 solutions=0 | Choose ). |
| 17 | L1 | depth=7 solutions=0 | Explore "((()))". |
| 18 | L2 | depth=7 solutions=1 | Complete and valid: record "((()))". |
| 19 | L11 | depth=6 solutions=1 | Unchoose ): back to "((())". |
| 20 | L11 | depth=5 solutions=1 | Unchoose ): back to "((()". |
| 21 | L11 | depth=4 solutions=1 | Unchoose ): back to "(((". |
| 22 | L6 | depth=3 solutions=1 | Unchoose (: back to "((". |
| 23 | L9 | depth=3 solutions=1 | Choose ). |
| 24 | L1 | depth=4 solutions=1 | Explore "(()". |
| 25 | L4 | depth=4 solutions=1 | Choose (. |
| 26 | L1 | depth=5 solutions=1 | Explore "(()(". |
| 27 | L3 | depth=6 solutions=1 | Reject (: all 3 opening parentheses are already used. |
| 28 | L9 | depth=5 solutions=1 | Choose ). |
| 29 | L1 | depth=6 solutions=1 | Explore "(()()". |
| 30 | L3 | depth=7 solutions=1 | Reject (: all 3 opening parentheses are already used. |
| 31 | L9 | depth=6 solutions=1 | Choose ). |
| 32 | L1 | depth=7 solutions=1 | Explore "(()())". |
| 33 | L2 | depth=7 solutions=2 | Complete and valid: record "(()())". |
| 34 | L11 | depth=6 solutions=2 | Unchoose ): back to "(()()". |
| 35 | L11 | depth=5 solutions=2 | Unchoose ): back to "(()(". |
| 36 | L6 | depth=4 solutions=2 | Unchoose (: back to "(()". |
| 37 | L9 | depth=4 solutions=2 | Choose ). |
| 38 | L1 | depth=5 solutions=2 | Explore "(())". |
| 39 | L4 | depth=5 solutions=2 | Choose (. |
| 40 | L1 | depth=6 solutions=2 | Explore "(())(". |
| 41 | L3 | depth=7 solutions=2 | Reject (: all 3 opening parentheses are already used. |
| 42 | L9 | depth=6 solutions=2 | Choose ). |
| 43 | L1 | depth=7 solutions=2 | Explore "(())()". |
| 44 | L2 | depth=7 solutions=3 | Complete and valid: record "(())()". |
| 45 | L11 | depth=6 solutions=3 | Unchoose ): back to "(())(". |
| 46 | L6 | depth=5 solutions=3 | Unchoose (: back to "(())". |
| 47 | L8 | depth=6 solutions=3 | Reject ): there is nothing open to close. |
| 48 | L11 | depth=4 solutions=3 | Unchoose ): back to "(()". |
| 49 | L11 | depth=3 solutions=3 | Unchoose ): back to "((". |
| 50 | L6 | depth=2 solutions=3 | Unchoose (: back to "(". |
| 51 | L9 | depth=2 solutions=3 | Choose ). |
| 52 | L1 | depth=3 solutions=3 | Explore "()". |
| 53 | L4 | depth=3 solutions=3 | Choose (. |
| 54 | L1 | depth=4 solutions=3 | Explore "()(". |
| 55 | L4 | depth=4 solutions=3 | Choose (. |
| 56 | L1 | depth=5 solutions=3 | Explore "()((". |
| 57 | L3 | depth=6 solutions=3 | Reject (: all 3 opening parentheses are already used. |
| 58 | L9 | depth=5 solutions=3 | Choose ). |
| 59 | L1 | depth=6 solutions=3 | Explore "()(()". |
| 60 | L3 | depth=7 solutions=3 | Reject (: all 3 opening parentheses are already used. |
| 61 | L9 | depth=6 solutions=3 | Choose ). |
| 62 | L1 | depth=7 solutions=3 | Explore "()(())". |
| 63 | L2 | depth=7 solutions=4 | Complete and valid: record "()(())". |
| 64 | L11 | depth=6 solutions=4 | Unchoose ): back to "()(()". |
| 65 | L11 | depth=5 solutions=4 | Unchoose ): back to "()((". |
| 66 | L6 | depth=4 solutions=4 | Unchoose (: back to "()(". |
| 67 | L9 | depth=4 solutions=4 | Choose ). |
| 68 | L1 | depth=5 solutions=4 | Explore "()()". |
| 69 | L4 | depth=5 solutions=4 | Choose (. |
| 70 | L1 | depth=6 solutions=4 | Explore "()()(". |
| 71 | L3 | depth=7 solutions=4 | Reject (: all 3 opening parentheses are already used. |
| 72 | L9 | depth=6 solutions=4 | Choose ). |
| 73 | L1 | depth=7 solutions=4 | Explore "()()()". |
| 74 | L2 | depth=7 solutions=5 | Complete and valid: record "()()()". |
| 75 | L11 | depth=6 solutions=5 | Unchoose ): back to "()()(". |
| 76 | L6 | depth=5 solutions=5 | Unchoose (: back to "()()". |
| 77 | L8 | depth=6 solutions=5 | Reject ): there is nothing open to close. |
| 78 | L11 | depth=4 solutions=5 | Unchoose ): back to "()(". |
| 79 | L6 | depth=3 solutions=5 | Unchoose (: back to "()". |
| 80 | L8 | depth=4 solutions=5 | Reject ): there is nothing open to close. |
| 81 | L11 | depth=2 solutions=5 | Unchoose ): back to "(". |
| 82 | L6 | depth=1 solutions=5 | Unchoose (: back to "". |
| 83 | L8 | depth=2 solutions=5 | Reject ): there is nothing open to close. |
| 84 | L13 | solutions=5 nodes=35 return=["((()))", "(()())", "(())()", "()(())", "()()()"] | Search finished: 5 solutions, 34 choices tried. Backtracking explores every branch the pruning rules allow. |
Palindrome partitioning
Cut a string into pieces so that every piece is a palindrome, and list all ways. At each position try every possible end of the next piece; if the piece is not a palindrome the branch is dead and is never explored. For "aab" the answers are [a, a, b] and [aa, b].
The animation marks each non-palindromic piece with ✗ and the reason. A palindrome test costs O(length); precomputing a table of palindromic substrings (Chapter 23) makes each test O(1).
1static List<List<String>> partitionPalindromes(String s) {2List<List<String>> out = new ArrayList<>();3cut(s, 0, new ArrayList<>(), out);4return out;5}6private static void cut(String s, int start, List<String> path, List<List<String>> out) {7if (start == s.length()) { out.add(new ArrayList<>(path)); return; }8for (int end = start + 1; end <= s.length(); end++) {9if (!isPal(s, start, end - 1)) continue; // prune: only palindromic pieces10path.add(s.substring(start, end));11cut(s, end, path, out);12path.remove(path.size() - 1);13}14}15private static boolean isPal(String s, int i, int j) {16while (i < j) if (s.charAt(i++) != s.charAt(j--)) return false;17return true;18}
Palindrome partitioning
Interactive · edit the inputpartial solution
solutions found (0)
Cut "aab" into pieces that are all palindromes. At each position try every next piece; a piece that is not a palindrome is rejected at once, so its whole subtree is never built.
1static void partition(String s, int start, List<String> path, List<List<String>> out) {2if (start == s.length()) { out.add(new ArrayList<>(path)); return; }3for (int end = start + 1; end <= s.length(); end++) {4if (!isPalindrome(s, start, end - 1)) continue; // prune: only palindromic pieces5path.add(s.substring(start, end)); // choose6partition(s, end, path, out); // explore7path.remove(path.size() - 1); // unchoose8}9}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 22 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Cut "aab" into pieces that are all palindromes. At each position try every next piece; a piece that is not a palindrome is rejected at once, so its whole subtree is never built. |
| 2 | L1 | depth=1 solutions=0 | Explore [] then "aab". |
| 3 | L5 | depth=1 solutions=0 | Choose "a". |
| 4 | L1 | depth=2 solutions=0 | Explore ["a"] then "ab". |
| 5 | L5 | depth=2 solutions=0 | Choose "a". |
| 6 | L1 | depth=3 solutions=0 | Explore ["a", "a"] then "b". |
| 7 | L5 | depth=3 solutions=0 | Choose "b". |
| 8 | L1 | depth=4 solutions=0 | Explore ["a", "a", "b"] then "". |
| 9 | L2 | depth=4 solutions=1 | Complete and valid: record [a, a, b]. |
| 10 | L7 | depth=3 solutions=1 | Unchoose "b": back to ["a", "a"] then "b". |
| 11 | L7 | depth=2 solutions=1 | Unchoose "a": back to ["a"] then "ab". |
| 12 | L4 | depth=3 solutions=1 | Reject "ab": "ab" is not a palindrome. |
| 13 | L7 | depth=1 solutions=1 | Unchoose "a": back to [] then "aab". |
| 14 | L5 | depth=1 solutions=1 | Choose "aa". |
| 15 | L1 | depth=2 solutions=1 | Explore ["aa"] then "b". |
| 16 | L5 | depth=2 solutions=1 | Choose "b". |
| 17 | L1 | depth=3 solutions=1 | Explore ["aa", "b"] then "". |
| 18 | L2 | depth=3 solutions=2 | Complete and valid: record [aa, b]. |
| 19 | L7 | depth=2 solutions=2 | Unchoose "b": back to ["aa"] then "b". |
| 20 | L7 | depth=1 solutions=2 | Unchoose "aa": back to [] then "aab". |
| 21 | L4 | depth=2 solutions=2 | Reject "aab": "aab" is not a palindrome. |
| 22 | L9 | solutions=2 nodes=8 return=[[a, a, b], [aa, b]] | Search finished: 2 solutions, 7 choices tried. Backtracking explores every branch the pruning rules allow. |
Letter combinations
Each digit 2-9 on a phone keypad stands for 3 or 4 letters. List every string a digit sequence can spell. At position i the options are the letters of digit i; there is no invalid option, so nothing is pruned and the tree has exactly one leaf per answer. The number of results is the product of the letter counts: for "23" it is 3 × 3 = 9.
This is the simplest backtracking shape, the Cartesian product, and a good template for any problem where each position has its own independent list of choices.
1static final String[] LETTERS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};23static List<String> letterCombinations(String digits) {4List<String> out = new ArrayList<>();5if (digits.isEmpty()) return out;6spell(digits, 0, new StringBuilder(), out);7return out;8}9private static void spell(String digits, int i, StringBuilder sb, List<String> out) {10if (i == digits.length()) { out.add(sb.toString()); return; }11for (char c : LETTERS[digits.charAt(i) - '0'].toCharArray()) {12sb.append(c);13spell(digits, i + 1, sb, out);14sb.deleteCharAt(sb.length() - 1);15}16}
Letter combinations of a phone number
Interactive · edit the inputpartial solution
solutions found (0)
Each digit offers its letters; a combination takes one letter per digit. There is nothing to prune: the tree has 9 leaves, one per combination.
1static final String[] LETTERS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};2static void combos(String digits, int i, StringBuilder sb, List<String> out) {3if (i == digits.length()) { out.add(sb.toString()); return; }4for (char c : LETTERS[digits.charAt(i) - '0'].toCharArray()) {5sb.append(c); // choose6combos(digits, i + 1, sb, out); // explore7sb.deleteCharAt(sb.length() - 1); // unchoose8}9}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 48 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | depth=0 solutions=0 | Each digit offers its letters; a combination takes one letter per digit. There is nothing to prune: the tree has 9 leaves, one per combination. |
| 2 | L2 | depth=1 solutions=0 | Explore "". |
| 3 | L5 | depth=1 solutions=0 | Choose a. |
| 4 | L2 | depth=2 solutions=0 | Explore "a". |
| 5 | L5 | depth=2 solutions=0 | Choose d. |
| 6 | L2 | depth=3 solutions=0 | Explore "ad". |
| 7 | L3 | depth=3 solutions=1 | Complete and valid: record "ad". |
| 8 | L7 | depth=2 solutions=1 | Unchoose d: back to "a". |
| 9 | L5 | depth=2 solutions=1 | Choose e. |
| 10 | L2 | depth=3 solutions=1 | Explore "ae". |
| 11 | L3 | depth=3 solutions=2 | Complete and valid: record "ae". |
| 12 | L7 | depth=2 solutions=2 | Unchoose e: back to "a". |
| 13 | L5 | depth=2 solutions=2 | Choose f. |
| 14 | L2 | depth=3 solutions=2 | Explore "af". |
| 15 | L3 | depth=3 solutions=3 | Complete and valid: record "af". |
| 16 | L7 | depth=2 solutions=3 | Unchoose f: back to "a". |
| 17 | L7 | depth=1 solutions=3 | Unchoose a: back to "". |
| 18 | L5 | depth=1 solutions=3 | Choose b. |
| 19 | L2 | depth=2 solutions=3 | Explore "b". |
| 20 | L5 | depth=2 solutions=3 | Choose d. |
| 21 | L2 | depth=3 solutions=3 | Explore "bd". |
| 22 | L3 | depth=3 solutions=4 | Complete and valid: record "bd". |
| 23 | L7 | depth=2 solutions=4 | Unchoose d: back to "b". |
| 24 | L5 | depth=2 solutions=4 | Choose e. |
| 25 | L2 | depth=3 solutions=4 | Explore "be". |
| 26 | L3 | depth=3 solutions=5 | Complete and valid: record "be". |
| 27 | L7 | depth=2 solutions=5 | Unchoose e: back to "b". |
| 28 | L5 | depth=2 solutions=5 | Choose f. |
| 29 | L2 | depth=3 solutions=5 | Explore "bf". |
| 30 | L3 | depth=3 solutions=6 | Complete and valid: record "bf". |
| 31 | L7 | depth=2 solutions=6 | Unchoose f: back to "b". |
| 32 | L7 | depth=1 solutions=6 | Unchoose b: back to "". |
| 33 | L5 | depth=1 solutions=6 | Choose c. |
| 34 | L2 | depth=2 solutions=6 | Explore "c". |
| 35 | L5 | depth=2 solutions=6 | Choose d. |
| 36 | L2 | depth=3 solutions=6 | Explore "cd". |
| 37 | L3 | depth=3 solutions=7 | Complete and valid: record "cd". |
| 38 | L7 | depth=2 solutions=7 | Unchoose d: back to "c". |
| 39 | L5 | depth=2 solutions=7 | Choose e. |
| 40 | L2 | depth=3 solutions=7 | Explore "ce". |
| 41 | L3 | depth=3 solutions=8 | Complete and valid: record "ce". |
| 42 | L7 | depth=2 solutions=8 | Unchoose e: back to "c". |
| 43 | L5 | depth=2 solutions=8 | Choose f. |
| 44 | L2 | depth=3 solutions=8 | Explore "cf". |
| 45 | L3 | depth=3 solutions=9 | Complete and valid: record "cf". |
| 46 | L7 | depth=2 solutions=9 | Unchoose f: back to "c". |
| 47 | L7 | depth=1 solutions=9 | Unchoose c: back to "". |
| 48 | L9 | solutions=9 nodes=13 return=["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] | Search finished: 9 solutions, 12 choices tried. Backtracking explores every branch the pruning rules allow. |
Restore IP addresses
Insert three dots into a digit string to make a valid IPv4 address: four numbers from 0 to 255 without leading zeros. Choose segment lengths 1, 2 or 3 at each step; reject a segment with a leading zero (except "0" itself) or a value above 255; and stop when four segments are chosen, accepting only if every digit was used.
For "25525511" the search tries up to 3 digits at each of 4 places and finds 7 valid addresses, among them 25.52.55.11 and 255.255.1.1. The tree stays tiny (69 nodes here) because lengths are capped at 3 and invalid segments are rejected immediately.
1static List<String> restoreIp(String s) {2List<String> out = new ArrayList<>();3segment(s, 0, new ArrayList<>(), out);4return out;5}6private static void segment(String s, int start, List<String> parts, List<String> out) {7if (parts.size() == 4) {8if (start == s.length()) out.add(String.join(".", parts)); // used every digit9return;10}11for (int len = 1; len <= 3 && start + len <= s.length(); len++) {12String seg = s.substring(start, start + len);13if ((seg.length() > 1 && seg.charAt(0) == '0') || Integer.parseInt(seg) > 255) continue; // invalid segment14parts.add(seg);15segment(s, start + len, parts, out);16parts.remove(parts.size() - 1);17}18}
Restore IP addresses
Interactive · edit the inputpartial solution
solutions found (0)
Cut "25525511" into exactly four numbers from 0 to 255 without leading zeros, using every digit. A segment has 1 to 3 digits; invalid segments are rejected immediately.
1static void restore(String s, int start, List<String> parts, List<String> out) {2if (parts.size() == 4) {3if (start == s.length()) out.add(String.join(".", parts)); // used every digit4return;5}6for (int len = 1; len <= 3 && start + len <= s.length(); len++) {7String seg = s.substring(start, start + len);8if ((seg.length() > 1 && seg.charAt(0) == '0') || Integer.parseInt(seg) > 255) continue; // invalid segment9parts.add(seg); // choose10restore(s, start + len, parts, out); // explore11parts.remove(parts.size() - 1); // unchoose12}13}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 210 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Cut "25525511" into exactly four numbers from 0 to 255 without leading zeros, using every digit. A segment has 1 to 3 digits; invalid segments are rejected immediately. |
| 2 | L1 | depth=1 solutions=0 | Explore (nothing yet) · 8 digits left. |
| 3 | L9 | depth=1 solutions=0 | Choose "2". |
| 4 | L1 | depth=2 solutions=0 | Explore 2 · 7 digits left. |
| 5 | L9 | depth=2 solutions=0 | Choose "5". |
| 6 | L1 | depth=3 solutions=0 | Explore 2.5 · 6 digits left. |
| 7 | L9 | depth=3 solutions=0 | Choose "5". |
| 8 | L1 | depth=4 solutions=0 | Explore 2.5.5 · 5 digits left. |
| 9 | L9 | depth=4 solutions=0 | Choose "2". |
| 10 | L1 | depth=5 solutions=0 | Explore 2.5.5.2 · 4 digits left. |
| 11 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 12 | L11 | depth=4 solutions=0 | Unchoose "2": back to 2.5.5 · 5 digits left. |
| 13 | L9 | depth=4 solutions=0 | Choose "25". |
| 14 | L1 | depth=5 solutions=0 | Explore 2.5.5.25 · 3 digits left. |
| 15 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 16 | L11 | depth=4 solutions=0 | Unchoose "25": back to 2.5.5 · 5 digits left. |
| 17 | L9 | depth=4 solutions=0 | Choose "255". |
| 18 | L1 | depth=5 solutions=0 | Explore 2.5.5.255 · 2 digits left. |
| 19 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 20 | L11 | depth=4 solutions=0 | Unchoose "255": back to 2.5.5 · 5 digits left. |
| 21 | L11 | depth=3 solutions=0 | Unchoose "5": back to 2.5 · 6 digits left. |
| 22 | L9 | depth=3 solutions=0 | Choose "52". |
| 23 | L1 | depth=4 solutions=0 | Explore 2.5.52 · 4 digits left. |
| 24 | L9 | depth=4 solutions=0 | Choose "5". |
| 25 | L1 | depth=5 solutions=0 | Explore 2.5.52.5 · 3 digits left. |
| 26 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 27 | L11 | depth=4 solutions=0 | Unchoose "5": back to 2.5.52 · 4 digits left. |
| 28 | L9 | depth=4 solutions=0 | Choose "55". |
| 29 | L1 | depth=5 solutions=0 | Explore 2.5.52.55 · 2 digits left. |
| 30 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 31 | L11 | depth=4 solutions=0 | Unchoose "55": back to 2.5.52 · 4 digits left. |
| 32 | L8 | depth=5 solutions=0 | Reject "551": 551 is above 255. |
| 33 | L11 | depth=3 solutions=0 | Unchoose "52": back to 2.5 · 6 digits left. |
| 34 | L8 | depth=4 solutions=0 | Reject "525": 525 is above 255. |
| 35 | L11 | depth=2 solutions=0 | Unchoose "5": back to 2 · 7 digits left. |
| 36 | L9 | depth=2 solutions=0 | Choose "55". |
| 37 | L1 | depth=3 solutions=0 | Explore 2.55 · 5 digits left. |
| 38 | L9 | depth=3 solutions=0 | Choose "2". |
| 39 | L1 | depth=4 solutions=0 | Explore 2.55.2 · 4 digits left. |
| 40 | L9 | depth=4 solutions=0 | Choose "5". |
| 41 | L1 | depth=5 solutions=0 | Explore 2.55.2.5 · 3 digits left. |
| 42 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 43 | L11 | depth=4 solutions=0 | Unchoose "5": back to 2.55.2 · 4 digits left. |
| 44 | L9 | depth=4 solutions=0 | Choose "55". |
| 45 | L1 | depth=5 solutions=0 | Explore 2.55.2.55 · 2 digits left. |
| 46 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 47 | L11 | depth=4 solutions=0 | Unchoose "55": back to 2.55.2 · 4 digits left. |
| 48 | L8 | depth=5 solutions=0 | Reject "551": 551 is above 255. |
| 49 | L11 | depth=3 solutions=0 | Unchoose "2": back to 2.55 · 5 digits left. |
| 50 | L9 | depth=3 solutions=0 | Choose "25". |
| 51 | L1 | depth=4 solutions=0 | Explore 2.55.25 · 3 digits left. |
| 52 | L9 | depth=4 solutions=0 | Choose "5". |
| 53 | L1 | depth=5 solutions=0 | Explore 2.55.25.5 · 2 digits left. |
| 54 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 55 | L11 | depth=4 solutions=0 | Unchoose "5": back to 2.55.25 · 3 digits left. |
| 56 | L9 | depth=4 solutions=0 | Choose "51". |
| 57 | L1 | depth=5 solutions=0 | Explore 2.55.25.51 · 1 digit left. |
| 58 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 59 | L11 | depth=4 solutions=0 | Unchoose "51": back to 2.55.25 · 3 digits left. |
| 60 | L8 | depth=5 solutions=0 | Reject "511": 511 is above 255. |
| 61 | L11 | depth=3 solutions=0 | Unchoose "25": back to 2.55 · 5 digits left. |
| 62 | L9 | depth=3 solutions=0 | Choose "255". |
| 63 | L1 | depth=4 solutions=0 | Explore 2.55.255 · 2 digits left. |
| 64 | L9 | depth=4 solutions=0 | Choose "1". |
| 65 | L1 | depth=5 solutions=0 | Explore 2.55.255.1 · 1 digit left. |
| 66 | L4 | depth=5 solutions=0 | Complete, but not a solution: dead end. |
| 67 | L11 | depth=4 solutions=0 | Unchoose "1": back to 2.55.255 · 2 digits left. |
| 68 | L9 | depth=4 solutions=0 | Choose "11". |
| 69 | L1 | depth=5 solutions=0 | Explore 2.55.255.11 · 0 digits left. |
| 70 | L3 | depth=5 solutions=1 | Complete and valid: record 2.55.255.11. |
| 71 | L11 | depth=4 solutions=1 | Unchoose "11": back to 2.55.255 · 2 digits left. |
| 72 | L11 | depth=3 solutions=1 | Unchoose "255": back to 2.55 · 5 digits left. |
| 73 | L11 | depth=2 solutions=1 | Unchoose "55": back to 2 · 7 digits left. |
| 74 | L8 | depth=3 solutions=1 | Reject "552": 552 is above 255. |
| 75 | L11 | depth=1 solutions=1 | Unchoose "2": back to (nothing yet) · 8 digits left. |
| 76 | L9 | depth=1 solutions=1 | Choose "25". |
| 77 | L1 | depth=2 solutions=1 | Explore 25 · 6 digits left. |
| 78 | L9 | depth=2 solutions=1 | Choose "5". |
| 79 | L1 | depth=3 solutions=1 | Explore 25.5 · 5 digits left. |
| 80 | L9 | depth=3 solutions=1 | Choose "2". |
| 81 | L1 | depth=4 solutions=1 | Explore 25.5.2 · 4 digits left. |
| 82 | L9 | depth=4 solutions=1 | Choose "5". |
| 83 | L1 | depth=5 solutions=1 | Explore 25.5.2.5 · 3 digits left. |
| 84 | L4 | depth=5 solutions=1 | Complete, but not a solution: dead end. |
| 85 | L11 | depth=4 solutions=1 | Unchoose "5": back to 25.5.2 · 4 digits left. |
| 86 | L9 | depth=4 solutions=1 | Choose "55". |
| 87 | L1 | depth=5 solutions=1 | Explore 25.5.2.55 · 2 digits left. |
| 88 | L4 | depth=5 solutions=1 | Complete, but not a solution: dead end. |
| 89 | L11 | depth=4 solutions=1 | Unchoose "55": back to 25.5.2 · 4 digits left. |
| 90 | L8 | depth=5 solutions=1 | Reject "551": 551 is above 255. |
| 91 | L11 | depth=3 solutions=1 | Unchoose "2": back to 25.5 · 5 digits left. |
| 92 | L9 | depth=3 solutions=1 | Choose "25". |
| 93 | L1 | depth=4 solutions=1 | Explore 25.5.25 · 3 digits left. |
| 94 | L9 | depth=4 solutions=1 | Choose "5". |
| 95 | L1 | depth=5 solutions=1 | Explore 25.5.25.5 · 2 digits left. |
| 96 | L4 | depth=5 solutions=1 | Complete, but not a solution: dead end. |
| 97 | L11 | depth=4 solutions=1 | Unchoose "5": back to 25.5.25 · 3 digits left. |
| 98 | L9 | depth=4 solutions=1 | Choose "51". |
| 99 | L1 | depth=5 solutions=1 | Explore 25.5.25.51 · 1 digit left. |
| 100 | L4 | depth=5 solutions=1 | Complete, but not a solution: dead end. |
| 101 | L11 | depth=4 solutions=1 | Unchoose "51": back to 25.5.25 · 3 digits left. |
| 102 | L8 | depth=5 solutions=1 | Reject "511": 511 is above 255. |
| 103 | L11 | depth=3 solutions=1 | Unchoose "25": back to 25.5 · 5 digits left. |
| 104 | L9 | depth=3 solutions=1 | Choose "255". |
| 105 | L1 | depth=4 solutions=1 | Explore 25.5.255 · 2 digits left. |
| 106 | L9 | depth=4 solutions=1 | Choose "1". |
| 107 | L1 | depth=5 solutions=1 | Explore 25.5.255.1 · 1 digit left. |
| 108 | L4 | depth=5 solutions=1 | Complete, but not a solution: dead end. |
| 109 | L11 | depth=4 solutions=1 | Unchoose "1": back to 25.5.255 · 2 digits left. |
| 110 | L9 | depth=4 solutions=1 | Choose "11". |
| 111 | L1 | depth=5 solutions=1 | Explore 25.5.255.11 · 0 digits left. |
| 112 | L3 | depth=5 solutions=2 | Complete and valid: record 25.5.255.11. |
| 113 | L11 | depth=4 solutions=2 | Unchoose "11": back to 25.5.255 · 2 digits left. |
| 114 | L11 | depth=3 solutions=2 | Unchoose "255": back to 25.5 · 5 digits left. |
| 115 | L11 | depth=2 solutions=2 | Unchoose "5": back to 25 · 6 digits left. |
| 116 | L9 | depth=2 solutions=2 | Choose "52". |
| 117 | L1 | depth=3 solutions=2 | Explore 25.52 · 4 digits left. |
| 118 | L9 | depth=3 solutions=2 | Choose "5". |
| 119 | L1 | depth=4 solutions=2 | Explore 25.52.5 · 3 digits left. |
| 120 | L9 | depth=4 solutions=2 | Choose "5". |
| 121 | L1 | depth=5 solutions=2 | Explore 25.52.5.5 · 2 digits left. |
| 122 | L4 | depth=5 solutions=2 | Complete, but not a solution: dead end. |
| 123 | L11 | depth=4 solutions=2 | Unchoose "5": back to 25.52.5 · 3 digits left. |
| 124 | L9 | depth=4 solutions=2 | Choose "51". |
| 125 | L1 | depth=5 solutions=2 | Explore 25.52.5.51 · 1 digit left. |
| 126 | L4 | depth=5 solutions=2 | Complete, but not a solution: dead end. |
| 127 | L11 | depth=4 solutions=2 | Unchoose "51": back to 25.52.5 · 3 digits left. |
| 128 | L8 | depth=5 solutions=2 | Reject "511": 511 is above 255. |
| 129 | L11 | depth=3 solutions=2 | Unchoose "5": back to 25.52 · 4 digits left. |
| 130 | L9 | depth=3 solutions=2 | Choose "55". |
| 131 | L1 | depth=4 solutions=2 | Explore 25.52.55 · 2 digits left. |
| 132 | L9 | depth=4 solutions=2 | Choose "1". |
| 133 | L1 | depth=5 solutions=2 | Explore 25.52.55.1 · 1 digit left. |
| 134 | L4 | depth=5 solutions=2 | Complete, but not a solution: dead end. |
| 135 | L11 | depth=4 solutions=2 | Unchoose "1": back to 25.52.55 · 2 digits left. |
| 136 | L9 | depth=4 solutions=2 | Choose "11". |
| 137 | L1 | depth=5 solutions=2 | Explore 25.52.55.11 · 0 digits left. |
| 138 | L3 | depth=5 solutions=3 | Complete and valid: record 25.52.55.11. |
| 139 | L11 | depth=4 solutions=3 | Unchoose "11": back to 25.52.55 · 2 digits left. |
| 140 | L11 | depth=3 solutions=3 | Unchoose "55": back to 25.52 · 4 digits left. |
| 141 | L8 | depth=4 solutions=3 | Reject "551": 551 is above 255. |
| 142 | L11 | depth=2 solutions=3 | Unchoose "52": back to 25 · 6 digits left. |
| 143 | L8 | depth=3 solutions=3 | Reject "525": 525 is above 255. |
| 144 | L11 | depth=1 solutions=3 | Unchoose "25": back to (nothing yet) · 8 digits left. |
| 145 | L9 | depth=1 solutions=3 | Choose "255". |
| 146 | L1 | depth=2 solutions=3 | Explore 255 · 5 digits left. |
| 147 | L9 | depth=2 solutions=3 | Choose "2". |
| 148 | L1 | depth=3 solutions=3 | Explore 255.2 · 4 digits left. |
| 149 | L9 | depth=3 solutions=3 | Choose "5". |
| 150 | L1 | depth=4 solutions=3 | Explore 255.2.5 · 3 digits left. |
| 151 | L9 | depth=4 solutions=3 | Choose "5". |
| 152 | L1 | depth=5 solutions=3 | Explore 255.2.5.5 · 2 digits left. |
| 153 | L4 | depth=5 solutions=3 | Complete, but not a solution: dead end. |
| 154 | L11 | depth=4 solutions=3 | Unchoose "5": back to 255.2.5 · 3 digits left. |
| 155 | L9 | depth=4 solutions=3 | Choose "51". |
| 156 | L1 | depth=5 solutions=3 | Explore 255.2.5.51 · 1 digit left. |
| 157 | L4 | depth=5 solutions=3 | Complete, but not a solution: dead end. |
| 158 | L11 | depth=4 solutions=3 | Unchoose "51": back to 255.2.5 · 3 digits left. |
| 159 | L8 | depth=5 solutions=3 | Reject "511": 511 is above 255. |
| 160 | L11 | depth=3 solutions=3 | Unchoose "5": back to 255.2 · 4 digits left. |
| 161 | L9 | depth=3 solutions=3 | Choose "55". |
| 162 | L1 | depth=4 solutions=3 | Explore 255.2.55 · 2 digits left. |
| 163 | L9 | depth=4 solutions=3 | Choose "1". |
| 164 | L1 | depth=5 solutions=3 | Explore 255.2.55.1 · 1 digit left. |
| 165 | L4 | depth=5 solutions=3 | Complete, but not a solution: dead end. |
| 166 | L11 | depth=4 solutions=3 | Unchoose "1": back to 255.2.55 · 2 digits left. |
| 167 | L9 | depth=4 solutions=3 | Choose "11". |
| 168 | L1 | depth=5 solutions=3 | Explore 255.2.55.11 · 0 digits left. |
| 169 | L3 | depth=5 solutions=4 | Complete and valid: record 255.2.55.11. |
| 170 | L11 | depth=4 solutions=4 | Unchoose "11": back to 255.2.55 · 2 digits left. |
| 171 | L11 | depth=3 solutions=4 | Unchoose "55": back to 255.2 · 4 digits left. |
| 172 | L8 | depth=4 solutions=4 | Reject "551": 551 is above 255. |
| 173 | L11 | depth=2 solutions=4 | Unchoose "2": back to 255 · 5 digits left. |
| 174 | L9 | depth=2 solutions=4 | Choose "25". |
| 175 | L1 | depth=3 solutions=4 | Explore 255.25 · 3 digits left. |
| 176 | L9 | depth=3 solutions=4 | Choose "5". |
| 177 | L1 | depth=4 solutions=4 | Explore 255.25.5 · 2 digits left. |
| 178 | L9 | depth=4 solutions=4 | Choose "1". |
| 179 | L1 | depth=5 solutions=4 | Explore 255.25.5.1 · 1 digit left. |
| 180 | L4 | depth=5 solutions=4 | Complete, but not a solution: dead end. |
| 181 | L11 | depth=4 solutions=4 | Unchoose "1": back to 255.25.5 · 2 digits left. |
| 182 | L9 | depth=4 solutions=4 | Choose "11". |
| 183 | L1 | depth=5 solutions=4 | Explore 255.25.5.11 · 0 digits left. |
| 184 | L3 | depth=5 solutions=5 | Complete and valid: record 255.25.5.11. |
| 185 | L11 | depth=4 solutions=5 | Unchoose "11": back to 255.25.5 · 2 digits left. |
| 186 | L11 | depth=3 solutions=5 | Unchoose "5": back to 255.25 · 3 digits left. |
| 187 | L9 | depth=3 solutions=5 | Choose "51". |
| 188 | L1 | depth=4 solutions=5 | Explore 255.25.51 · 1 digit left. |
| 189 | L9 | depth=4 solutions=5 | Choose "1". |
| 190 | L1 | depth=5 solutions=5 | Explore 255.25.51.1 · 0 digits left. |
| 191 | L3 | depth=5 solutions=6 | Complete and valid: record 255.25.51.1. |
| 192 | L11 | depth=4 solutions=6 | Unchoose "1": back to 255.25.51 · 1 digit left. |
| 193 | L11 | depth=3 solutions=6 | Unchoose "51": back to 255.25 · 3 digits left. |
| 194 | L8 | depth=4 solutions=6 | Reject "511": 511 is above 255. |
| 195 | L11 | depth=2 solutions=6 | Unchoose "25": back to 255 · 5 digits left. |
| 196 | L9 | depth=2 solutions=6 | Choose "255". |
| 197 | L1 | depth=3 solutions=6 | Explore 255.255 · 2 digits left. |
| 198 | L9 | depth=3 solutions=6 | Choose "1". |
| 199 | L1 | depth=4 solutions=6 | Explore 255.255.1 · 1 digit left. |
| 200 | L9 | depth=4 solutions=6 | Choose "1". |
| 201 | L1 | depth=5 solutions=6 | Explore 255.255.1.1 · 0 digits left. |
| 202 | L3 | depth=5 solutions=7 | Complete and valid: record 255.255.1.1. |
| 203 | L11 | depth=4 solutions=7 | Unchoose "1": back to 255.255.1 · 1 digit left. |
| 204 | L11 | depth=3 solutions=7 | Unchoose "1": back to 255.255 · 2 digits left. |
| 205 | L9 | depth=3 solutions=7 | Choose "11". |
| 206 | L1 | depth=4 solutions=7 | Explore 255.255.11 · 0 digits left. |
| 207 | L11 | depth=3 solutions=7 | Unchoose "11": back to 255.255 · 2 digits left. |
| 208 | L11 | depth=2 solutions=7 | Unchoose "255": back to 255 · 5 digits left. |
| 209 | L11 | depth=1 solutions=7 | Unchoose "255": back to (nothing yet) · 8 digits left. |
| 210 | L13 | solutions=7 nodes=69 return=[2.55.255.11, 25.5.255.11, 25.52.55.11, 255.2.55.11, 255.25.5.11, 255.25.51.1, 255.255.1.1] | Search finished: 7 solutions, 68 choices tried. Backtracking explores every branch the pruning rules allow. |
Word break enumeration
Given a string and a dictionary, list every way to split the string into dictionary words. At each position try every prefix; recurse only if the prefix is a word. For "catsand" with {cat, cats, and, sand} the sentences are "cat sand" and "cats and". The number of sentences can be exponential (the string "aaaa" with {a, aa, aaa} has 7), so listing them is inherently expensive.
If you only need to know *whether* a split exists, memoise on the start index and the problem becomes O(n²) (Chapter 24). The animation rejects non-words with ✗ and the dictionary reason.
1static List<String> wordBreak(String s, Set<String> dict) {2List<String> out = new ArrayList<>();3split(s, 0, dict, new ArrayList<>(), out);4return out;5}6private static void split(String s, int start, Set<String> dict, List<String> words, List<String> out) {7if (start == s.length()) { out.add(String.join(" ", words)); return; }8for (int end = start + 1; end <= s.length(); end++) {9String word = s.substring(start, end);10if (!dict.contains(word)) continue; // prune: only dictionary words11words.add(word);12split(s, end, dict, words, out);13words.remove(words.size() - 1);14}15}
Word break: enumerate every sentence
Interactive · edit the inputpartial solution
solutions found (0)
Split "catsand" into words from {cat, cats, and, sand}. At each position try every prefix; only dictionary words are explored. Without the check the tree would try all 2^6 ways to cut the string.
1static void wordBreak(String s, int start, Set<String> dict, List<String> words, List<String> out) {2if (start == s.length()) { out.add(String.join(" ", words)); return; }3for (int end = start + 1; end <= s.length(); end++) {4String word = s.substring(start, end);5if (!dict.contains(word)) continue; // prune: only dictionary words6words.add(word); // choose7wordBreak(s, end, dict, words, out); // explore8words.remove(words.size() - 1); // unchoose9}10}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 27 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Split "catsand" into words from {cat, cats, and, sand}. At each position try every prefix; only dictionary words are explored. Without the check the tree would try all 2^6 ways to cut the string. |
| 2 | L1 | depth=1 solutions=0 | Explore "" + "catsand" left. |
| 3 | L5 | depth=2 solutions=0 | Reject "c": "c" is not in the dictionary. |
| 4 | L5 | depth=2 solutions=0 | Reject "ca": "ca" is not in the dictionary. |
| 5 | L6 | depth=1 solutions=0 | Choose "cat". |
| 6 | L1 | depth=2 solutions=0 | Explore "cat" + "sand" left. |
| 7 | L5 | depth=3 solutions=0 | Reject "s": "s" is not in the dictionary. |
| 8 | L5 | depth=3 solutions=0 | Reject "sa": "sa" is not in the dictionary. |
| 9 | L5 | depth=3 solutions=0 | Reject "san": "san" is not in the dictionary. |
| 10 | L6 | depth=2 solutions=0 | Choose "sand". |
| 11 | L1 | depth=3 solutions=0 | Explore "cat sand" + "" left. |
| 12 | L2 | depth=3 solutions=1 | Complete and valid: record "cat sand". |
| 13 | L8 | depth=2 solutions=1 | Unchoose "sand": back to "cat" + "sand" left. |
| 14 | L8 | depth=1 solutions=1 | Unchoose "cat": back to "" + "catsand" left. |
| 15 | L6 | depth=1 solutions=1 | Choose "cats". |
| 16 | L1 | depth=2 solutions=1 | Explore "cats" + "and" left. |
| 17 | L5 | depth=3 solutions=1 | Reject "a": "a" is not in the dictionary. |
| 18 | L5 | depth=3 solutions=1 | Reject "an": "an" is not in the dictionary. |
| 19 | L6 | depth=2 solutions=1 | Choose "and". |
| 20 | L1 | depth=3 solutions=1 | Explore "cats and" + "" left. |
| 21 | L2 | depth=3 solutions=2 | Complete and valid: record "cats and". |
| 22 | L8 | depth=2 solutions=2 | Unchoose "and": back to "cats" + "and" left. |
| 23 | L8 | depth=1 solutions=2 | Unchoose "cats": back to "" + "catsand" left. |
| 24 | L5 | depth=2 solutions=2 | Reject "catsa": "catsa" is not in the dictionary. |
| 25 | L5 | depth=2 solutions=2 | Reject "catsan": "catsan" is not in the dictionary. |
| 26 | L5 | depth=2 solutions=2 | Reject "catsand": "catsand" is not in the dictionary. |
| 27 | L10 | solutions=2 nodes=15 return=["cat sand", "cats and"] | Search finished: 2 solutions, 14 choices tried. Backtracking explores every branch the pruning rules allow. |
Practice ladder
Now try it yourself
Easy
- Letter Case Permutation
- Binary Watch
Medium
- Generate Parentheses
- Letter Combinations of a Phone Number
- Palindrome Partitioning
- Restore IP Addresses
Advanced
- Word Break II
- Remove Invalid Parentheses
- Expression Add Operators
Key takeaways
- Build strings with StringBuilder: append to choose, deleteCharAt to unchoose.
- Test a piece against its rule (palindrome, range, dictionary) before recursing.
- The number of results can be exponential: counting or existence questions need memoisation instead.