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

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

相关推荐
csdn_aspnet3 小时前
C/C++ 两个凸多边形之间的切线(Tangents between two Convex Polygons)
c语言·c++·算法
开源盛世!!3 小时前
3.23-3.25笔记
笔记
数据皮皮侠3 小时前
中国城市间地理距离矩阵(2024)
大数据·数据库·人工智能·算法·制造
3GPP仿真实验室3 小时前
深度解析基站接收机核心算法:从 MRC 到 IRC 的空间滤波演进
算法
Boop_wu3 小时前
[Java 算法] 动态规划(1)
算法·动态规划
WolfGang0073213 小时前
代码随想录算法训练营 Day18 | 二叉树 part08
算法
hanlin034 小时前
刷题笔记:力扣第43、67题(字符串计算)
笔记·算法·leetcode
yang_B6214 小时前
最小二乘法 拟合平面
算法·平面·最小二乘法
放下华子我只抽RuiKe54 小时前
深度学习全景指南:硬核实战版
人工智能·深度学习·神经网络·算法·机器学习·自然语言处理·数据挖掘