Backtracking Algorithm Approach: Core Concepts and Common Patterns for Problem Solving
Backtracking is a depth-first search technique that incrementally builds solution candidates and abandons invalid paths through "pruning," following a systematic "choose-explore-undo" cycle implemented across the azl397985856/leetcode repository.
The backtracking algorithm approach serves as a fundamental strategy for solving constraint satisfaction problems by exploring a state tree of decisions. According to the repository's detailed guide at thinkings/backtrack.en.md, this method combines exhaustive enumeration with intelligent pruning to efficiently search solution spaces without exploring impossible branches.
Core Mechanics of the Backtracking Algorithm Approach
Backtracking operates as a refined depth-first search (DFS) that constructs solutions incrementally. The algorithm visualizes the problem as a decision tree where each node represents a partial solution, traversing paths recursively while eliminating dead ends.
The approach follows five essential steps:
- Construct a state tree – Each level represents a new decision, such as selecting the next element or movement direction.
- Traverse recursively – Use DFS to explore each branch of the decision tree.
- Prune invalid branches – Abandon paths that violate constraints or cannot possibly lead to a valid solution.
- Record valid solutions – Capture complete solutions when terminal nodes satisfy target conditions.
- Undo decisions – Restore the previous state before exploring alternative branches.
This "try, fail, and back up" methodology ensures the algorithm only commits resources to viable paths, making it essential for combinatorial search problems where brute force would be computationally infeasible.
Common Patterns in Backtracking Implementation
The azl397985856/leetcode repository identifies five recurring patterns that demonstrate how to apply the backtracking algorithm approach to different problem categories.
Subset and Combination Generation
This pattern addresses problems like "Generate all subsets" or "Combination Sum" by iterating through elements and making binary include-or-exclude decisions. The implementation uses a start index to prevent duplicate combinations, recursively building subsets while avoiding reprocessing earlier elements.
Key file reference: The pattern is fully documented in problems/78.subsets.md, which demonstrates how to generate all possible subsets of an array without repetition.
Permutation and Full Arrangement
For problems requiring all possible orderings of elements (such as LeetCode 46), this pattern tracks used elements through a visited array or set. At each recursive depth, the algorithm selects any unused element, marks it as visited, explores deeper levels, then unmarks it during backtracking to allow reuse in other branches.
Key file reference: See problems/46.permutations.md for the complete implementation using a visited boolean array to manage element selection.
Path-Finding in Grids and Boards
This pattern applies to spatial search problems like "Word Search," "Sudoku," and "N-Queens." The algorithm moves in four or eight directional steps across a matrix, maintaining a visited matrix to track current path occupancy. When a move proves illegal or completes the target word/path, the algorithm backtracks by clearing the visited flag and tries alternative directions.
Key file reference: The grid traversal technique with visited-matrix handling appears in problems/79.word-search.md.
Partition and Split Problems
Problems like "Palindrome Partitioning" or "Split Array into Fibonacci Sequence" require dividing input strings at every possible index. The algorithm recursively processes the suffix of each split, validating constraints (such as numeric limits or sequence ordering) before continuing deeper into the recursion.
Key file reference: Advanced pruning strategies for partition problems are detailed in the "Pruning" section of thinkings/backtrack.en.md (lines 64-70).
State-Space Pruning Optimization
This meta-pattern enhances any backtracking implementation by adding constraint checks before recursion. Early validation eliminates large subtrees by checking conditions like sum overflow, leading zeros in numeric strings, or impossible remaining sums, significantly reducing the search space.
Practical Code Examples from the Repository
Subset Generation (LeetCode 78) – JavaScript
The following implementation from problems/78.subsets.md demonstrates the include-or-exclude pattern using a start index to avoid duplicates:
function subsets(nums) {
const result = [];
function backtrack(start, path) {
result.push([...path]); // record current subset
for (let i = start; i < nums.length; i++) {
path.push(nums[i]); // choose
backtrack(i + 1, path); // explore
path.pop(); // undo
}
}
backtrack(0, []);
return result;
}
The start parameter ensures each recursive call only considers elements after the current index, preventing the generation of duplicate subsets like [1,2] and [2,1].
Permutation Generation (LeetCode 46) – Python
As implemented in problems/46.permutations.md, this solution uses a visited array to track selected elements:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
visited = [False] * len(nums)
def dfs(path):
if len(path) == len(nums):
res.append(path[:]) # store a copy
return
for i in range(len(nums)):
if visited[i]:
continue
visited[i] = True
path.append(nums[i])
dfs(path) # go deeper
path.pop() # undo
visited[i] = False
dfs([])
return res
The algorithm marks visited[i] = True before recursion and resets it to False during backtracking, allowing the same element to participate in different positions across separate permutation branches.
Word Search (LeetCode 79) – Java
This grid-based implementation from problems/79.word-search.md demonstrates directional movement with state restoration:
class Solution {
private int m, n;
private char[][] board;
private String word;
private boolean[][] visited;
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
m = board.length;
n = board[0].length;
visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (dfs(i, j, 0)) return true;
}
}
return false;
}
private boolean dfs(int i, int j, int idx) {
if (idx == word.length()) return true;
if (i < 0 || i >= m || j < 0 || j >= n ||
visited[i][j] || board[i][j] != word.charAt(idx))
return false;
visited[i][j] = true;
boolean found = dfs(i+1, j, idx+1) ||
dfs(i-1, j, idx+1) ||
dfs(i, j+1, idx+1) ||
dfs(i, j-1, idx+1);
visited[i][j] = false; // undo
return found;
}
}
The visited[i][j] = false line constitutes the critical backtracking step, clearing the cell's occupied status before the algorithm explores alternative paths from previous decision points.
Pruned Fibonacci Split – Python
This advanced example from the backtracking guide demonstrates aggressive state-space pruning:
class Solution:
def splitIntoFibonacci(self, S: str) -> List[int]:
def backtrack(start, path):
# prune: length constraint
if len(path) > 2 and path[-1] != path[-2] + path[-3]:
return []
if start == len(S):
return path if len(path) >= 3 else []
cur = 0
for i in range(start, len(S)):
# prune: leading zero
if i > start and S[start] == '0':
break
cur = cur * 10 + int(S[i])
# prune: overflow
if cur > 2**31 - 1:
break
path.append(cur)
ans = backtrack(i + 1, path)
if ans:
return ans
path.pop() # undo
return []
return backtrack(0, [])
The implementation applies three pruning layers: Fibonacci sequence validation, leading zero prevention, and integer overflow checking, eliminating invalid branches before expensive recursive calls.
Summary
- The backtracking algorithm approach is a DFS-based method that builds solutions incrementally while abandoning invalid paths through pruning.
- Five common patterns emerge from the azl397985856/leetcode repository: Subset Generation, Permutation Tracking, Grid Path-Finding, String Partitioning, and State-Space Pruning.
- The universal "choose-explore-undo" template requires explicitly recording choices, recursing to deeper levels, and restoring state before exploring alternatives.
- Critical implementation files include
thinkings/backtrack.en.mdfor theoretical foundations and problem-specific files likeproblems/78.subsets.mdandproblems/46.permutations.mdfor concrete examples. - Effective pruning—checking constraints before recursion—is essential for optimizing backtracking performance on large input spaces.
Frequently Asked Questions
What distinguishes backtracking from standard recursion?
Backtracking is a specialized form of recursion that systematically explores decision trees and explicitly undoes choices (restores state) after exploring each branch. While standard recursion may simply divide problems into subproblems, backtracking specifically targets constraint satisfaction by pruning invalid paths and backtracking to previous decision points when branches fail, as demonstrated in the state restoration patterns found in problems/79.word-search.md.
How do I choose between a visited array and a start index in backtracking?
Use a visited array for permutation problems where element order matters but reuse is prohibited within a single path, and use a start index for combination/subset problems where element order does not matter. The start index technique (shown in problems/78.subsets.md) prevents generating duplicate combinations by only considering elements after the current position, while the visited array (used in problems/46.permutations.md) tracks which specific elements are currently in the partial solution path.
When should I apply pruning in a backtracking solution?
Apply pruning immediately before making recursive calls to eliminate branches that violate constraints or cannot possibly lead to valid solutions. According to thinkings/backtrack.en.md, effective pruning checks include validating numeric ranges, detecting leading zeros, verifying sum conditions (as in problems/39.combination-sum.md), and checking diagonal conflicts (in problems/52.N-Queens-II.md), all of which prevent the algorithm from wasting time exploring doomed subtrees.
Can backtracking solve problems with large input sizes?
Backtracking is generally suited for problems with moderate input sizes (typically N ≤ 20-30) because its exponential time complexity makes it impractical for massive search spaces without aggressive pruning. The approach remains viable for larger inputs only when combined with sophisticated pruning strategies or constraint propagation that significantly reduce the effective search tree, as illustrated by the optimized Fibonacci split implementation in the repository's backtracking guide.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →