目录
【LeetCode | 第六篇】算法笔记
https://blog.csdn.net/h52412224/article/details/159078357
【LeetCode | 第五篇】算法笔记
https://blog.csdn.net/h52412224/article/details/159043914【LeetCode | 第四篇】算法笔记
https://blog.csdn.net/h52412224/article/details/159018487
回溯
全排列


思路1:
java
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return result;
used = new boolean[nums.length];
backtrack(nums);
return result;
}
// 回溯核心:构建以当前path为前缀的全排列
private void backtrack(int[] nums) {
if (path.size() == nums.length) { // 路径满则记录结果
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue; // 跳过已使用元素
used[i] = true; // 标记使用
path.add(nums[i]); // 加入路径
backtrack(nums); // 递归构建剩余路径
path.removeLast(); // 回溯:移除最后一个元素
used[i] = false; // 回溯:取消标记
}
}
}
思路2
java
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return result;
backtrack(nums);
return result;
}
// 回溯核心:通过path.contains判断是否已选当前元素
private void backtrack(int[] nums) {
if (path.size() == nums.length) { // 路径满则记录结果
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (path.contains(nums[i])) continue; // 跳过已选元素
path.add(nums[i]); // 加入路径
backtrack(nums); // 递归构建剩余路径
path.removeLast(); // 回溯:移除最后一个元素
}
}
}
子集

思路:
-
以
startIndex控制遍历起点,避免重复子集(如 [1,2] 和 [2,1] 视为同一子集); -
每进入递归先记录当前路径(空集、单元素、多元素均为有效子集);
-
遍历数组时从
startIndex开始选元素,递归后回溯撤销选择,覆盖所有组合可能。 -

java
class Solution {
private List<Integer> list = new ArrayList<>(); // 记录当前子集路径
private List<List<Integer>> total_list = new ArrayList<>(); // 存放所有子集结果
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0); // 从索引0开始回溯
return total_list;
}
// 回溯核心:startIndex控制遍历起点,避免重复子集
void backtracking(int[] nums, int startIndex) {
total_list.add(new ArrayList<>(list)); // 先记录当前路径(空集/部分子集)
if (list.size() == nums.length) return; // 子集长度等于数组长度时终止
// 从startIndex开始遍历,保证子集元素按顺序选取,不重复
for (int i = startIndex; i < nums.length; i++) {
list.add(nums[i]); // 选择当前元素加入路径
backtracking(nums, i + 1); // 递归:下一个元素从i+1开始(避免回头选)
list.removeLast(); // 回溯:移除最后一个元素,尝试其他选择
}
}
}
电话号码的字母组合

思路:
-
先建立数字到字母的映射表,将输入数字串按位拆解;
-
以
level标记当前处理的数字位,递归遍历每一位数字对应的所有字母; -
每选一个字母就进入下一位递归,所有位处理完时记录组合结果,回溯撤销选择继续尝试其他字母。

java
class Solution {
private List<String> list = new ArrayList<>(); // 存放最终字母组合结果
// 数字到字母的映射表(索引对应数字0-9)
private String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
private StringBuilder temp = new StringBuilder(); // 记录当前字母组合
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) return list; // 空输入直接返回
backTracking(digits, 0); // 从第0位数字开始回溯
return list;
}
// 回溯核心:level表示当前处理到digits的第几位
private void backTracking(String digits, int level) {
if (level == digits.length()) { // 所有数字位处理完,记录结果
list.add(temp.toString());
return;
}
// 获取当前位数字对应的字母串(char转int:减去'0'的ASCII值)
String str = numString[digits.charAt(level) - '0'];
// 遍历当前数字对应的所有字母
for (int i = 0; i < str.length(); i++) {
temp.append(str.charAt(i)); // 选择当前字母
backTracking(digits, level + 1); // 递归处理下一位数字
temp.deleteCharAt(temp.length() - 1); // 回溯:删除最后一个字母
}
}
}
组合总和

思路:
-
允许元素重复选取 ,通过
startIndex控制遍历起点避免重复组合(如 [2,3] 和 [3,2] 视为同一组合); -
递归过程中累加路径和
sum,超过目标值则剪枝,等于目标值则记录结果; -
回溯撤销选择(移除元素、回退 sum),继续尝试其他组合。

java
class Solution {
private List<Integer> path = new ArrayList<>(); // 记录当前组合路径
private List<List<Integer>> result = new ArrayList<>(); // 存放所有符合条件的组合
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0) return result; // 空输入直接返回
backTracking(candidates, target, 0, 0); // 从索引0、和为0开始回溯
return result;
}
// 回溯核心:sum为当前路径和,startIndex控制遍历起点(避免重复组合)
private void backTracking(int[] candidates, int target, int sum, int startIndex) {
if (sum > target) return; // 剪枝:和超过目标,直接返回
if (sum == target) { // 和等于目标,记录结果
result.add(new ArrayList<>(path));
return;
}
// 从startIndex开始遍历,避免重复组合(如[2,3]和[3,2])
for (int i = startIndex; i < candidates.length; i++) {
path.add(candidates[i]); // 选择当前元素
sum += candidates[i]; // 累加和
backTracking(candidates, target, sum, i); // 递归:允许重复选当前元素(i不+1)
path.removeLast(); // 回溯:移除最后一个元素
sum -= candidates[i]; // 回溯:回退和
}
}
}
括号生成

思路:
-
暴力枚举所有可能的括号组合(长度为 2n);
-
每一步递归要么加左括号,要么加右括号;
-
组合长度达到 2n 时,校验括号有效性,有效则记录结果,回溯撤销选择继续枚举。
java
class Solution {
private List<String> result; // 存放有效括号组合结果
private StringBuilder current; // 记录当前括号组合
public List<String> generateParenthesis(int n) {
result = new ArrayList<>();
current = new StringBuilder();
backtrack(n);
return result;
}
// 回溯核心:枚举所有括号组合,长度达标后校验有效性
private void backtrack(int n) {
if (current.length() == 2 * n) { // 组合长度达到2n(n对括号)
if (valid(current)) { // 校验括号是否有效
result.add(current.toString());
}
return;
}
// 尝试添加左括号
current.append('(');
backtrack(n);
current.deleteCharAt(current.length() - 1); // 回溯:删除最后一个字符
// 尝试添加右括号
current.append(')');
backtrack(n);
current.deleteCharAt(current.length() - 1); // 回溯:删除最后一个字符
}
// 校验括号有效性:平衡值始终≥0,最终为0
private boolean valid(StringBuilder sb) {
int balance = 0; // 括号平衡值(左+1,右-1)
for (int i = 0; i < sb.length(); i++) {
balance += sb.charAt(i) == '(' ? 1 : -1;
if (balance < 0) return false; // 右括号多于左括号,直接无效
}
return balance == 0; // 最终左右括号数量相等
}
}
思路2:
-
用
left记录左括号数量,right记录右括号数量; -
左括号数量 < n 时,才能添加左括号(保证不超总数);
-
右括号数量 < 左括号时,才能添加右括号(保证括号有效);
-
组合长度达到 2n 时,直接记录结果(剪枝后必为有效组合)。
java
class Solution {
private List<String> result; // 存放有效括号组合结果
private StringBuilder current; // 记录当前括号组合
public List<String> generateParenthesis(int n) {
result = new ArrayList<>();
current = new StringBuilder();
backtrack(0, 0, n); // 初始左、右括号数为0
return result;
}
// 回溯核心:left=左括号数,right=右括号数,n=总对数
private void backtrack(int left, int right, int n) {
if (current.length() == 2 * n) { // 组合长度达标(n对括号)
result.add(current.toString()); // 剪枝后无需校验,直接记录
return;
}
// 剪枝1:左括号数 < n 时,才能加左括号
if (left < n) {
current.append('(');
backtrack(left + 1, right, n); // 左括号数+1
current.deleteCharAt(current.length() - 1); // 回溯:删除最后一个字符
}
// 剪枝2:右括号数 < 左括号数 时,才能加右括号(保证有效)
if (right < left) {
current.append(')');
backtrack(left, right + 1, n); // 右括号数+1
current.deleteCharAt(current.length() - 1); // 回溯:删除最后一个字符
}
}
}
上述内容也同步在我的飞书,欢迎访问
https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!