Strings and Grids
These problems are not about choosing a number from an array, but they use the same shape:
- build a valid sequence one character at a time;
- choose one letter for the next output position;
- choose the next valid substring;
- choose the next grid neighbor.
The DFS state must describe how much of the input has been consumed and which current-path restrictions apply.
Pattern summary
| Problem | DFS state | One branch means | Important invariant |
|---|---|---|---|
| Generate Parentheses | open count, close count | add one legal parenthesis | closes never exceed opens |
| Letter Combinations | digit index | choose one mapped letter | path length equals processed digits |
| Palindrome Partitioning | string start index | choose one valid next piece | path pieces exactly cover the prefix |
| Word Search | row, column, word index | move to one adjacent matching cell | a cell is visited only in the current path |
9. Generate Parentheses
Recognition
Build a string of n opening and n closing parentheses, but never create a prefix that cannot be repaired. The two legal choices are determined by counts:
- add
"("while fewer thannopens have been used; - add
")"only whileclose < open.
The second condition is the key invariant: every prefix has at least as many opens as closes.
DFS contract
dfs(open, close) adds every valid completion of the current path, which already contains open opening and close closing parentheses.
JavaScript
function generateParenthesis(n) {
const result = [];
const path = [];
function dfs(open, close) {
if (open === n && close === n) {
result.push(path.join(""));
return;
}
if (open < n) {
path.push("(");
dfs(open + 1, close);
path.pop();
}
if (close < open) {
path.push(")");
dfs(open, close + 1);
path.pop();
}
}
dfs(0, 0);
return result;
}Why no invalid strings are generated
A closing parenthesis is never chosen unless there is an unmatched opening parenthesis to pair it with. At the final length, the string therefore has equal counts and every prefix is balanced—exactly the definition of a valid parenthesis string.
- Time:
O(Cn * n), whereCnis the nth Catalan number (the number of valid answers). - Auxiliary space:
O(n), excluding output.
10. Letter Combinations of a Phone Number
Recognition
Each digit independently offers three or four letters. This is a simple product-of-choices tree: one letter per digit position.
DFS contract
dfs(i) generates every letter string whose prefix is path, after making choices for digits before index i.
JavaScript
function letterCombinations(digits) {
if (digits.length === 0) return [];
const letters = {
2: "abc",
3: "def",
4: "ghi",
5: "jkl",
6: "mno",
7: "pqrs",
8: "tuv",
9: "wxyz",
};
const result = [];
const path = [];
function dfs(i) {
if (i === digits.length) {
result.push(path.join(""));
return;
}
for (const letter of letters[digits[i]]) {
path.push(letter);
dfs(i + 1);
path.pop();
}
}
dfs(0);
return result;
}- Time:
O(n * 4^n)— at most four choices per digit andO(n)to build each returned string. - Auxiliary space:
O(n), excluding output.
11. Palindrome Partitioning
Recognition
At the current string index, the decision is not “take or skip a character.” It is:
Where should the next cut end?
Try every ending position, but recurse only when the chosen substring is a palindrome.
DFS contract
dfs(start) produces every palindrome partition of the suffix beginning at start; path already partitions s[0...start - 1].
JavaScript
function partition(s) {
const result = [];
const path = [];
function isPalindrome(left, right) {
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
function dfs(start) {
if (start === s.length) {
result.push([...path]);
return;
}
for (let end = start; end < s.length; end++) {
if (!isPalindrome(start, end)) continue;
path.push(s.slice(start, end + 1));
dfs(end + 1);
path.pop();
}
}
dfs(0);
return result;
}Correctness cue
Every recursive call moves start to exactly one position after the selected piece. Therefore the path contains contiguous, non-overlapping pieces that cover the original string exactly when start === s.length.
- Time:
O(n * 2^n)in the usual output-sensitive bound. - Auxiliary space:
O(n)for recursion and path, excluding output. - Upgrade path: precompute a palindrome table when palindrome checks become the bottleneck.
12. Word Search
Recognition
The word may start anywhere, so launch DFS from every cell. A path may move up, down, left, or right, but may not reuse a cell in that one path.
DFS contract
dfs(r, c, i) returns whether a path beginning at (r, c) can match word[i...]. Before it runs, cells on the route to this one are marked visited.
JavaScript
function exist(board, word) {
const rows = board.length;
const cols = board[0].length;
function dfs(r, c, i) {
if (i === word.length) return true;
if (
r < 0 || r === rows ||
c < 0 || c === cols ||
board[r][c] !== word[i]
) {
return false;
}
const saved = board[r][c];
board[r][c] = "#"; // mark this cell for this path only
const found =
dfs(r + 1, c, i + 1) ||
dfs(r - 1, c, i + 1) ||
dfs(r, c + 1, i + 1) ||
dfs(r, c - 1, i + 1);
board[r][c] = saved; // always restore before returning
return found;
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (dfs(r, c, 0)) return true;
}
}
return false;
}Why restoration matters even after success
The found expression evaluates the needed branches, then the function restores the cell before returning its boolean answer. That preserves the input and keeps the DFS contract true. Returning directly from a branch before restoration is a common bug.
- Time:
O(rows * cols * 4 * 3^(L - 1)), whereLis word length. - Auxiliary space:
O(L)recursion / current path.
Practical pruning
Before DFS, return false if the word is longer than the number of cells. You can also compare character frequencies in the board and word, or reverse the word to begin with its rarer endpoint. These reduce work but do not change the recurrence.
Recall prompts
| Problem | Say this before coding |
|---|---|
| Generate Parentheses | “Add an open if I can; add a close only if it can match an earlier open.” |
| Phone combinations | “At each digit, choose one mapped letter and advance one position.” |
| Palindrome partitioning | “At each start index, try every next cut that forms a palindrome.” |
| Word Search | “From every start cell, match one character, mark the cell, explore four neighbors, then restore it.” |
Next: 05 - Constraint Search