• 递归回溯实战+思想


    目录

    排列(提供元素无重复,并且不可以重复选择)

    排列(提供的元素重复了,但是同个位置的元素不能复选)

    组合(提供的元素没有重复,并且可以重复选择相同位置元素)

    子集(提供的元素没有重复,且同位置元素只能选择一次)

    组合(提供元素不重复,且同位置不能重复选)


     

    排列(提供元素无重复,并且不可以重复选择)

    思路:1.根据题意找到base case——>2.然后遍历提供的数据节点,(不可重复选择同一数据节点,并且提供的数据节点都没有重复)——>3.我们利用boolean数据进行判断,当前数据节点是否选择,如果选择过直接continue——>4.没有选择过进行选择逻辑并且修改状态——>5.进入递归函数——>6.回溯,撤销逻辑选择

     模板:

    1. /* 组合/子集问题回溯算法框架 */
    2. void backtrack(int[] nums, int start) {
    3. // 回溯算法标准框架
    4. for (int i = start; i < nums.length; i++) {
    5. // 做选择
    6. track.addLast(nums[i]);
    7. // 注意参数
    8. backtrack(nums, i + 1);
    9. // 撤销选择
    10. track.removeLast();
    11. }
    12. }
    13. /* 排列问题回溯算法框架 */
    14. void backtrack(int[] nums) {
    15. for (int i = 0; i < nums.length; i++) {
    16. // 剪枝逻辑
    17. if (used[i]) {
    18. continue;
    19. }
    20. // 做选择
    21. used[i] = true;
    22. track.addLast(nums[i]);
    23. backtrack(nums);
    24. // 撤销选择
    25. track.removeLast();
    26. used[i] = false;
    27. }
    28. }

    例题:

    46. 全排列 - 力扣(LeetCode)

    1. //1.结果集
    2. List>res=new LinkedList<>();
    3. public List> permute(int[] nums) {
    4. //1.记录一条路径
    5. LinkedListtrack = new LinkedList<>();
    6. //2.记录每个元素状态
    7. boolean[] used=new boolean[nums.length];
    8. backtrack(nums,track,used);
    9. return res;
    10. }
    11. void backtrack(int[]nums,LinkedListtrack,boolean[]used){
    12. //1.结束条件
    13. if(track.size()==nums.length){
    14. res.add(new LinkedList(track));
    15. return;
    16. }
    17. //2.遍历每个元素
    18. for(int i=0;i
    19. //2.1排除不合法的选择
    20. if(used[i]){
    21. continue;
    22. }
    23. //2.2对当前元素做选择
    24. track.add(nums[i]);
    25. used[i]=true;
    26. backtrack(nums,track,used);
    27. //2.3进行回溯
    28. track.removeLast();
    29. used[i]=false;
    30. }
    31. }

    排列(提供的元素重复了,但是同个位置的元素不能复选)

    不同之处:和上述思路差不多,加了一个剪枝操作,因为提供的元素有重复(比如:【1,2,2】),所以我们需要固定重复元素的位置,防止出现重复结果

    打个比方就以 nums = [1,2,2] 为例,为了区别两个 2 是不同元素,后面我们写作 nums = [1,2,2']

    ——>显然,两条值相同的相邻树枝会产生重复

     剪枝:需要先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1]&&used[i-1] (排列情况)

    比如输入 nums = [1,2,2',2'']2' 只有在 2 已经被使用的情况下才会被选择,同理,2'' 只有在 2' 已经被使用的情况下才会被选择,这就保证了相同元素在排列中的相对位置保证固定

    if (i > 0 && nums[i] == nums[i - 1] && used[i-1]) continue;

     模板:

    1. Arrays.sort(nums);
    2. /* 组合/子集问题回溯算法框架 */
    3. void backtrack(int[] nums, int start) {
    4. // 回溯算法标准框架
    5. for (int i = start; i < nums.length; i++) {
    6. // 剪枝逻辑,跳过值相同的相邻树枝
    7. if (i > start && nums[i] == nums[i - 1]) {
    8. continue;
    9. }
    10. // 做选择
    11. track.addLast(nums[i]);
    12. // 注意参数
    13. backtrack(nums, i + 1);
    14. // 撤销选择
    15. track.removeLast();
    16. }
    17. }
    18. Arrays.sort(nums);
    19. /* 排列问题回溯算法框架 */
    20. void backtrack(int[] nums) {
    21. for (int i = 0; i < nums.length; i++) {
    22. // 剪枝逻辑
    23. if (used[i]) {
    24. continue;
    25. }
    26. // 剪枝逻辑,固定相同的元素在排列中的相对位置
    27. if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
    28. continue;
    29. }
    30. // 做选择
    31. used[i] = true;
    32. track.addLast(nums[i]);
    33. backtrack(nums);
    34. // 撤销选择
    35. track.removeLast();
    36. used[i] = false;
    37. }
    38. }

    例题:

    47. 全排列 II - 力扣(LeetCode)

    1. class Solution {
    2. List> perResult = new LinkedList<>();
    3. LinkedList perTrack = new LinkedList<>();
    4. boolean[] used;
    5. public List> permuteUnique(int[] nums) {
    6. //1.先排序
    7. Arrays.sort(nums);
    8. used = new boolean[nums.length];
    9. //2.递归函数
    10. permuteUniqueHelper(nums);
    11. return perResult;
    12. }
    13. void permuteUniqueHelper(int[] nums) {
    14. //1.base:子路径的结束条件
    15. if (perTrack.size() == nums.length) {
    16. perResult.add(new LinkedList<>(perTrack));
    17. return;
    18. }
    19. //2.遍历nums所有数据
    20. for (int i = 0; i < nums.length; i++) {
    21. //2.1判断当前节点有没有使用过——>使用过就不能再次出现
    22. if (used[i]) continue;
    23. //2.2剪枝逻辑,固定排序位置
    24. if (i > 0 && nums[i] == nums[i - 1] && used[i-1]) continue;
    25. //2.3添加当前节点进子序列
    26. perTrack.add(nums[i]);
    27. used[i]=true;
    28. permuteUniqueHelper(nums);
    29. //2.4回溯
    30. perTrack.removeLast();
    31. used[i]=false;
    32. }
    33. }
    34. }

    组合(提供的元素没有重复,并且可以重复选择相同位置元素)

    一般作用于求和类组合题目上,像这种提供元素不重复(重复元素,要考虑去重->剪枝即可),可以重新选择(删除去重逻辑->start不+1)

    39. 组合总和 - 力扣(LeetCode)

    1. class Solution {
    2. List> res = new LinkedList<>();
    3. // 记录回溯的路径
    4. LinkedList track = new LinkedList<>();
    5. // 记录 track 中的路径和
    6. int trackSum = 0;
    7. public List> combinationSum(int[] candidates, int target) {
    8. if (candidates.length == 0) {
    9. return res;
    10. }
    11. backtrack(candidates, 0, target);
    12. return res;
    13. }
    14. // 回溯算法主函数
    15. void backtrack(int[] nums, int start, int target) {
    16. // base case,找到目标和,记录结果
    17. if (trackSum == target) {
    18. res.add(new LinkedList<>(track));
    19. return;
    20. }
    21. // base case,超过目标和,停止向下遍历
    22. if (trackSum > target) {
    23. return;
    24. }
    25. // 回溯算法标准框架
    26. for (int i = start; i < nums.length; i++) {
    27. // 选择 nums[i]
    28. trackSum += nums[i];
    29. track.add(nums[i]);
    30. // 递归遍历下一层回溯树
    31. // 同一元素可重复使用,注意参数
    32. backtrack(nums, i, target);
    33. // 撤销选择 nums[i]
    34. trackSum -= nums[i];
    35. track.removeLast();
    36. }
    37. }
    38. }

    子集(提供的元素没有重复,且同位置元素只能选择一次)

    思路:只能选择一次,每次进入递归的时候,利用一个变量+1(start+1)防止重复踩到相同位置元素——>1.因为是组合类题目,允许空集,所以前序位置直接结果集加一个子序列——>2.每次添加一个元素进入递归函数后——>结果集都会重新加一个子序列(也是通过start控制相同位置不重复访问的,利用track子序列添加元素,然后递归后慢慢撤销回溯,但是像我们以下的图,到i=2他就进不了1这个位置了)

    模板:

    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. }

     例题:

    78. 子集 - 力扣(LeetCode)

    1. class Solution {
    2. //1.结果集
    3. List> result=new LinkedList<>();
    4. public List> subsets(int[] nums) {
    5. //2.单节点路径
    6. LinkedList track=new LinkedList<>();
    7. //3.递归函数
    8. back(nums,0,track);
    9. return result;
    10. }
    11. void back(int[]nums,int start,LinkedListtrack){
    12. //1.首先添加一个空集合,对于后面就是添加有数据的节点
    13. result.add(new LinkedList<>(track));
    14. //2.回溯得到组合框架
    15. for(int i=start;i
    16. //2.1添加节点
    17. track.addLast(nums[i]);
    18. //2.2进行递归
    19. back(nums,i+1,track);
    20. //2.3回溯
    21. track.removeLast();
    22. }
    23. }
    24. }

    组合(提供元素不重复,且同位置不能重复选)

    思路:跟子集一样,

    你比如说,让你在 nums = [1,2,3] 中拿 2 个元素形成所有的组合,你怎么做?

    稍微想想就会发现,大小为 2 的所有组合,不就是所有大小为 2 的子集嘛。

    所以我说组合和子集是一样的:大小为 k 的组合就是大小为 k 的子集

     

    77. 组合 - 力扣(LeetCode)

    1. class Solution {
    2. /**
    3. * 3.组合问题,找出给定数组中指定个数的组合
    4. *
    5. * @param n:指定范围1-n
    6. * @param k:指定个数k个
    7. * @return
    8. */
    9. List> res = new LinkedList<>();
    10. // 记录回溯算法的递归路径
    11. LinkedList track = new LinkedList<>();
    12. // 主函数
    13. public List> combine(int n, int k) {
    14. backtrack(1, n, k);
    15. return res;
    16. }
    17. void backtrack(int start, int n, int k) {
    18. // base case
    19. if (k == track.size()) {
    20. // 遍历到了第 k 层,收集当前节点的值
    21. res.add(new LinkedList<>(track));
    22. return;
    23. }
    24. // 回溯算法标准框架
    25. for (int i = start; i <= n; i++) {
    26. // 选择
    27. track.addLast(i);
    28. // 通过 start 参数控制树枝的遍历,避免产生重复的子集
    29. backtrack(i + 1, n, k);
    30. // 撤销选择
    31. track.removeLast();
    32. }
    33. }
    34. }

  • 相关阅读:
    redis 发布者订阅者实例
    HTML+CSS+JS网页设计期末课程大作业 web前端开发技术 web课程设计 网页规划与设计
    Android14之java层:增加系统API(二百二十)
    SpringCloud微服务实战——搭建企业级开发框架(三十九):使用Redis分布式锁(Redisson)+自定义注解+AOP实现微服务重复请求控制
    心理咨询预约微信小程序开发制作步骤
    【案例】3D地球(vue+three.js)
    Docker13:容器互联----link (给新手玩的,进阶方法是 自定义网络)
    IDEA启动项目很慢,无访问
    JDBC 版本和历史
    PID控制算法学习笔记分享
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/126925534