【LeetCode | 第七篇】算法笔记

目录

回溯

全排列

子集

电话号码的字母组合

组合总和

括号生成


【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,22,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,33,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

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!

相关推荐
xxxiugou123几秒前
双指针解题秘籍:从入门到精通
c语言·数据结构·c++·算法
能源革命1 分钟前
TimesFM(Time Series Foundation Model)介绍
算法·能源
猎嘤一号5 分钟前
学术训练(一)文献检索与阅读
人工智能·算法·机器学习·科研入门
弘毅 失败的 mian18 分钟前
数组和指针基础
c语言·开发语言·经验分享·笔记
Doubbbbbbble云19 分钟前
多路归并算法在外部排序中的实现与优化
算法
沉淀的.晴天39 分钟前
FreeRTOS信号量
数据结构·算法
问心无愧051341 分钟前
ctf show web 177
前端·笔记
青山木1 小时前
Hot 100 --- 打家劫舍
java·数据结构·算法·leetcode·动态规划
机器学习之心1 小时前
均值、中值、圆周期:三种 InSAR 干涉相位滤波的实测对比与参数取舍
算法·均值算法
xy20262 小时前
多轮 Tool Agent 训不动?先把环境 token 从 loss 里拿掉
算法