关于回溯算法的去重

1,树层去重:

当题目给的集合中有重复的数字,但是要求结果不能相同,此时应该去重

有两种树层去重的方法:

方法一:当不需要考虑原集合的顺序时,可采用排序去重法

如例题:力扣90. 子集 II(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客

90.子集II :给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

集合中有重复的元素,但是组合要求不能重复,需要去重(sort)

    //去重需要排序
    nums.sort()
    .......
    //同一层重复的元素剪枝
    if(i>index&&nums[i]==nums[i-1]){
          continue
     }

方法二:当需要考虑原集合的顺序,不能打乱顺序,则不能使用排序法,可以用标记法

如例题:力扣491. 递增子序列(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客

491.递增子序列 :给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

用数组use标记每层元素的使用情况,如果使用了,则需要标记为true,则跳过此次循环

let use=[]
        for(let i=index;i<nums.length;i++){
            if(p.length>0&&nums[i]<p[p.length-1]){
                continue
            }
            //如果没出现过为undefined
            if(use[nums[i]]){
                continue
            }
            //记录出现过的数
            use[nums[i]]=true
            p.push(nums[i])
            dd(i+1)
            p.pop()
        }
 
    }

2,树枝去重:

什么时候需要树枝去重:在全排列问题中,递归到下一层的选择范围总是整个集合,只需要排除掉已经加入path的节点,所以需要采用树枝的去重

树枝去重只有一种方法:标记法

如例题:力扣46. 全排列(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客

46.全排列:给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]
  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

因为是树枝去重,涉及递归,所以把use当作参数传递,当回溯的时候也要记得去除标记

for(let i=0;i<nums.length;i++){
            //排除当前path中出现过的值
            if(use[nums[i]]){
                continue
            }
            p.push(nums[i])
            use[nums[i]]=true
            dd(use)
            p.pop()
            use[nums[i]]=false  //回溯时,组合中出现过的值,需要去除标记
        }

3,树层去重和树枝去重的双应用

如例题:力扣47. 全排列 II(JavaScript)_csD_Dscnnnnnnn的博客-CSDN博客

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

示例 1:

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

分析题目:集合中有重复的元素,且要求结果不能重复,需要树层去重(排序法),因为是全排列问题,每一层的选择范围都是整个集合(除了同一个树枝中使用过的元素),所以需要树枝去重(标记法)

什么时候可以跳过循环呢?

        1,树层的跳出:当前元素和前一个元素相等,且use【i-1】值为false(撤销标记的记录,同层使用过相等的值,因为没有标记过的话,use的值应该为undefined),则continue

        2,树枝去重:在递归中传递use,如果加入path则标记为true,当use为true时,continue

需要注意的是:本次保存在use中的是排序过后的集合的下标

for(let i=0;i<len;i++){
            //数层去重
            if(i>0&&nums[i]==nums[i-1]&&use[i-1]==false){
                continue
            }
            //树枝去重
            if(use[i]){
                continue
            }
            use[i]=true
            p.push(nums[i])
            dd(use)
            p.pop()
            use[i]=false
        }

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯法是一种解决组合问题的有效方法。组合总和等于0的问题可以通过回溯法来解决。下面是基本的回溯法求解过程: 1. 定义一个数组用来存储符合条件的组合,以及一个变量用来记录当前组合的和。 2. 对原始数组进行排序,以便于后续的剪枝。 3. 从原始数组的第一个元素开始,依次枚举所有的元素。 4. 对于每个元素,如果当前组合的和加上该元素小于等于0,则将该元素加入到组合中,并更新当前组合的和。 5. 如果当前组合的和等于0,则将该组合加入到结果数组中。 6. 对于每个元素,如果当前组合的和加上该元素大于0,则不再继续向下搜索,直接返回上一层回溯。 7. 如果当前组合的和加上该元素等于0,则将该元素从组合中删除,并更新当前组合的和。 8. 重复步骤4-7,直到枚举完所有元素。 9. 返回结果数组。 下面是Python的代码实现: ```python def combinationSum(nums): nums.sort() # 排序 res = [] def backtrack(path, target, start): if target == 0: # 找到符合条件的组合 res.append(path) return for i in range(start, len(nums)): if nums[i] > target: break # 剪枝 if i > start and nums[i] == nums[i-1]: continue # 去重 backtrack(path+[nums[i]], target-nums[i], i+1) backtrack([], 0, 0) return res ``` 该算法的时间复杂度为O(2^n),其中n为数组的长度。由于需要遍历所有可能的组合,因此时间复杂度比较高。同时,由于需要存储所有符合条件的组合,空间复杂度也比较高。因此,在实际应用中需要考虑性能问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值