• labuladong算法——回溯框架


    解决一个回溯问题,实际上就是一个决策树的遍历过程,站在回溯树的一个节点上,你只需要思考 3 个问题:

    1、路径:也就是已经做出的选择。

    2、选择列表:也就是你当前可以做的选择。

    3、结束条件:也就是到达决策树底层,无法再做选择的条件。

    1. result = []
    2. def backtrack(路径, 选择列表):
    3. if 满足结束条件:
    4. result.add(路径)
    5. return
    6. for 选择 in 选择列表:
    7. 做选择
    8. backtrack(路径, 选择列表)
    9. 撤销选择

    其核心就在于for循环中的backtrack递归,在递归之前做出选择,在递归之后撤销选择。


    一、全排列问题 LC46

    给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

     然后找到框架去重新审视:

    1. result = []
    2. def backtrack(路径, 选择列表):
    3. if 满足结束条件:
    4. result.add(路径)
    5. return
    6. for 选择 in 选择列表:
    7. 将选择从选择列表中移除
    8. 路径.add(选择)
    9. backtrack(路径, 选择列表)
    10. 路径.remove(选择)
    11. 将选择加入选择列表
    1. import java.util.LinkedList;
    2. //leetcode submit region begin(Prohibit modification and deletion)
    3. class Solution {
    4. //外围放一个双重list保存结果
    5. List> res = new LinkedList<>();
    6. public List> permute(int[] nums) {
    7. //这个track存放的是res里面的某个list,比如[1,2,3],
    8. //当然也有[1,2]的时候,就是不断往里加.
    9. LinkedList track = new LinkedList<>();
    10. //这个boolean的used主要是标记用了没用
    11. //比如123 如果是0就标记1已经用过了,然后把1拿出来放到track里
    12. boolean[] used = new boolean[nums.length];
    13. //传入递归的东西:原始数组 正在递归的track 是否用过的used
    14. trackback(nums, track, used);//不需要返回值
    15. return res;
    16. }
    17. void trackback(int[] nums,LinkedList track,boolean[] used){
    18. //当nums中所有的数字都被用完的时候,返回
    19. if(track.size()==nums.length){
    20. res.add(new LinkedList(track));
    21. return;
    22. }
    23. //遍历nums中的每个数字
    24. //注意 每次迭代都要遍历 但是如果遍历过的就直接过
    25. for (int i = 0; i < nums.length; i++) {
    26. if(used[i]){
    27. continue;
    28. }
    29. //给track加上最后一位
    30. track.add(nums[i]);
    31. //标记已经用过了
    32. used[i] = true;
    33. trackback(nums,track,used);
    34. track.removeLast();
    35. used[i] = false;
    36. }
    37. }
    38. }
    39. //leetcode submit region end(Prohibit modification and deletion)

    二、子集(元素无重不可复选)LC78

    题目给你输入一个无重复元素的数组 nums,其中每个元素最多使用一次,请你返回 nums 的所有子集。

     这道题一个显而易见的问题在于 1 2 3 和 2 1 3是相同的。

    这里我们采用一个start来标记当前遍历到哪个位置了——比如1 这个时候start就是 1 代表0位置的1已经被遍历了,接下来只需要考虑2和3就行了。

    这个start应该在递归里面传递下去,每次创建新的track都需要start

    1. List> res = new LinkedList<>();
    2. // 记录回溯算法的递归路径
    3. LinkedList track = new LinkedList<>();
    4. // 主函数
    5. public List> subsets(int[] nums) {
    6. backtrack(nums, 0);
    7. return res;
    8. }
    9. // 回溯算法核心函数,遍历子集问题的回溯树
    10. void backtrack(int[] nums, int start) {
    11. // 前序位置,每个节点的值都是一个子集
    12. res.add(new LinkedList<>(track));
    13. // 回溯算法标准框架
    14. for (int i = start; i < nums.length; i++) {
    15. // 做选择
    16. track.addLast(nums[i]);
    17. // 通过 start 参数控制树枝的遍历,避免产生重复的子集
    18. backtrack(nums, i + 1);
    19. // 撤销选择
    20. track.removeLast();
    21. }
    22. }

    三、组合(元素无重不可复选)

    给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

    1. import java.util.LinkedList;
    2. //leetcode submit region begin(Prohibit modification and deletion)
    3. class Solution {
    4. List> res = new LinkedList<>();
    5. LinkedList track = new LinkedList<>();
    6. public List> combine(int n, int k) {
    7. back(1, n, k);
    8. return res;
    9. }
    10. void back(int start,int n,int k){
    11. if(track.size()==k){
    12. res.add(new LinkedList<>(track));
    13. return;
    14. }
    15. for (int i = start; i <= n; i++) {
    16. track.add(i);
    17. back(i+1, n, k);
    18. track.removeLast();
    19. }
    20. }
    21. }
    22. //leetcode submit region end(Prohibit modification and deletion)

    容易迷惑的点有两个,一个是n是从1开始的,这样才能保证track=k的时候,track里面有k个元素。

    另外一个是用start标志的,因为这里面不是元素,是顺序的数组,实际上不用start也可以用一个nums数组来标记,不过start明显更方便。


    四、子集/组合(元素可重不可复选)

    给你一个整数数组 nums,其中可能包含重复元素,请你返回该数组所有可能的子集。

    比如输入 nums = [1,2,2],你应该输出:

    [ [],[1],[2],[1,2],[2,2],[1,2,2] ]

     

    这个的问题也很简单:需要先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1],则跳过

    1. import java.util.Arrays;
    2. //leetcode submit region begin(Prohibit modification and deletion)
    3. class Solution {
    4. List> res = new LinkedList<>();
    5. // 记录回溯算法的递归路径
    6. LinkedList track = new LinkedList<>();
    7. public List> subsetsWithDup(int[] nums) {
    8. Arrays.sort(nums);//先排个序
    9. backtrack(nums, 0);
    10. return res;
    11. }
    12. void backtrack(int[] nums, int start) {
    13. // 前序位置,每个节点的值都是一个子集
    14. res.add(new LinkedList<>(track));
    15. // 回溯算法标准框架
    16. for (int i = start; i < nums.length; i++) {
    17. // 做选择
    18. if (i > start && nums[i] == nums[i - 1]) continue;
    19. //剪枝逻辑 如果等于就跳过 注意这里是从0开始的所以必须判断一下
    20. track.addLast(nums[i]);
    21. // 通过 start 参数控制树枝的遍历,避免产生重复的子集
    22. backtrack(nums, i + 1);
    23. // 撤销选择
    24. track.removeLast();
    25. }
    26. }
    27. }
    28. //leetcode submit region end(Prohibit modification and deletion)

    五、排列(元素可重不可复选)

    给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

    输入:nums = [1,1,2] 输出:
    [[1,1,2],[1,2,1],[2,1,1]]

     最关键的就是脑子清醒一点,知道在递归过程中哪步是干什么的。

    这里我们用一个prenum保存上一个枝的数字——千万注意,这个枝是在迭代之前就要保存好的,比如1 1 3 在回溯到第二个1之后,要判断和上个1是否相同,如果相同就直接跳过。

    1. import java.util.LinkedList;
    2. //leetcode submit region begin(Prohibit modification and deletion)
    3. class Solution {
    4. List> res = new LinkedList<>();
    5. LinkedList track =new LinkedList<>();
    6. boolean[] used;
    7. public List> permuteUnique(int[] nums) {
    8. used = new boolean[nums.length];
    9. back(nums);
    10. return res;
    11. }
    12. void back(int[] nums){
    13. int prenum = -100;
    14. if(track.size()==nums.length){
    15. res.add(new LinkedList<>(track));
    16. return;
    17. }
    18. for (int i = 0; i < nums.length; i++) {
    19. if(used[i]||prenum==nums[i]){
    20. continue;
    21. }
    22. //这里就是最关键的剪枝逻辑 prenum在进入迭代之前就要保存好
    23. //迭代完成,完成i=1这个下面的树迭代之后,i=2的时候,才开始比对prenum和i=2的时候的1
    24. //也就是说 比较的其实是i=1时候的枝和i=2时候的枝
    25. track.add(nums[i]);
    26. prenum = nums[i];
    27. used[i] = true;
    28. back(nums);
    29. track.removeLast();
    30. used[i] = false;
    31. }
    32. }
    33. }
    34. //leetcode submit region end(Prohibit modification and deletion)

  • 相关阅读:
    python 中内置函数ord()返回字符串的ASCII数值
    机器学习——异常检测
    js filter,every,includes 过滤数组
    JVM内存模型介绍
    电商仓储物流是什么?
    k8s(Kubernetes)集群部署--使用 kubeadm方式部署
    【AIGC】如何在使用stable-diffusion-webui生成图片时看到完整请求参数
    Modbus协议详解2:通信方式、地址规则、主从机通信状态
    Spring关于注解的使用
    ubuntu VNC 配置
  • 原文地址:https://blog.csdn.net/qq_37772958/article/details/126865831