voidbacktrack(int startIndex, List<Integer> path, List<List<Integer>> res) { // 1. Base case: when to stop? if (终止条件) { res.add(newArrayList<>(path)); return; }
// 2. Loop through choices for (inti= startIndex; i < n; i++) { // 3. Choose path.add(nums[i]);
If we pick element at index i: - Current level: uses nums[i] - Next level: starts from index i + 1 - Result: avoids reusing nums[i] or earlier elements
为什么这能防止重复:
Branch 1 (start=0): picks nums[0]=1 → then search range [1,2,3…]
Branch 2 (start=1): picks nums[1]=2 → then search range [2,3…]
Branch 3 (start=2): picks nums[2]=3 → then search range [3…]
Result: [1,2] and [2,1] never both appear
剪枝:必要性,而非优化
提前终止逻辑
剪枝消除不可能成功的路径:
1 2 3 4 5
Example: Choose k elements from n Remaining elements needed: k - path.size() Remaining elements available: n - i + 1
If (n - i + 1 < k - path.size()) → impossible to succeed → skip
约束条件:i <= n - (k - path.size()) + 1
变量
含义
n
可用元素总数
k
目标元素数量
path.size()
已选择的元素数量
n - i + 1
剩余元素数量(包含当前元素)
剪枝实践
1 2 3 4 5 6 7 8 9 10 11 12 13
// Without pruning - explores unnecessary branches for (inti= startIndex; i <= n; i++) { path.add(nums[i]); backtrack(i + 1, path, res); path.remove(path.size() - 1); }
// With pruning - skips impossible branches for (inti= startIndex; i <= n - (k - path.size()) + 1; i++) { path.add(nums[i]); backtrack(i + 1, path, res); path.remove(path.size() - 1); }
去重策略
为什么去重很重要
没有去重时,包含重复值的问题会产生重复的结果:
输入
不去重
去重后
[1,1,2]
[[1,1,2],[1,2,1],[1,1,2],[1,2,1],…]
[[1,1,2],[1,2,1]]
i > startIndex 模式
1
if (i > startIndex && nums[i] == nums[i - 1]) continue;
正确理解:
i > startIndex:我们在同一树层(同一决策深度)
nums[i] == nums[i-1]:与前一个分支相同的值
含义:避免在同一层级内两次选择相同的值
它不意味着:
❌ “输入中不允许有重复值”
❌ “结果中不允许有重复值”
从易到难:题目递进
组合
问题:从 n 个元素中选择 k 个,顺序无关
关键约束: startIndex + i + 1
模板:
1 2 3 4 5 6 7 8 9
voidcombine(int n, int k) { backtrack(1, n, k, newArrayList<>(), result); }
for (inti= startIndex; i <= n - (k - path.size()) + 1; i++) { path.add(i); backtrack(i + 1, n, k, path, res); path.remove(path.size() - 1); }
classSolution { public List<List<Integer>> combine(int n, int k) { List<List<Integer>> res = newArrayList<>(); backtrack(1, n, k, newArrayList<>(), res); return res; }
privatevoidbacktrack(int startIndex, int n, int k, List<Integer> path, List<List<Integer>> res) { if (path.size() == k) { res.add(newArrayList<>(path)); return; } for (inti= startIndex; i <= n - (k - path.size()) + 1; i++) { path.add(i); backtrack(i + 1, n, k, path, res); path.remove(path.size() - 1); } } }
classSolution { public List<List<String>> solveNQueens(int n) { List<List<String>> res = newArrayList<>(); char[][] board = newchar[n][n]; for (char[] row : board) Arrays.fill('.'); boolean[] cols = newboolean[n]; boolean[] diag1 = newboolean[2 * n - 1]; boolean[] diag2 = newboolean[2 * n - 1]; backtrack(0, n, board, cols, diag1, diag2, res); return res; }
privatevoidbacktrack(int row, int n, char[][] board, boolean[] cols, boolean[] diag1, boolean[] diag2, List<List<String>> res) { if (row == n) { List<String> solution = newArrayList<>(); for (char[] r : board) solution.add(newString(r)); res.add(solution); return; } for (intcol=0; col < n; col++) { if (cols[col] || diag1[row - col + n - 1] || diag2[row + col]) continue; board[row][col] = 'Q'; cols[col] = true; diag1[row - col + n - 1] = true; diag2[row + col] = true; backtrack(row + 1, n, board, cols, diag1, diag2, res); board[row][col] = '.'; cols[col] = false; diag1[row - col + n - 1] = false; diag2[row + col] = false; } } }
树的遍历,n=4(2 个结果):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Row 0 ├── Col 0 (Q at 0,0) │ ├── Col 2 (Q at 1,2) - diagonal conflict, skip │ └── Col 3 (Q at 1,3) - diagonal conflict, skip │ → No solution from this branch ├── Col 1 (Q at 0,1) │ ├── Col 3 (Q at 1,3) │ │ ├── Col 0 (Q at 2,0) │ │ │ └── Col 2 (Q at 3,2) → Valid! [".Q..","...Q","Q...","..Q."] │ └── Col 0 (Q at 1,0) │ └── ... (symmetric to above) ├── Col 2 (Q at 0,2) - symmetric to Col 1 └── Col 3 (Q at 0,3) - symmetric to Col 0 Total: 2 solutions