代码随想录训练营Day29 回溯算法part05* 491.递增子序列* 46.全排列* 47.全排列 II

491.递增子序列

题目链接:491. 递增子序列 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)

视频链接: 回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列

 这道题乍一看难点有俩,第一点就是树层上不能遍历重复的数组,第二点是树枝上元素必须是递增的。

第一点,需要去重,而由于数组的顺序已经固定,所以原先的方法,先排序在使用used数组判断的方法就无法使用了。我们还可以使用set哈希结构记录每一层用过的数组,之后同一层次遍历时都查找这个set结构,若有就直接跳过。这样就实现了树层上的去重了。

第二点,判断树枝上元素递增也好说,一个if判定,如果递归到一个小的数,就直接跳过。

这两点可以合并成一个if解决,代码如下:

if((path.empty()==false&&nums[i]<path.back())||uset.find(nums[i])!=uset.end()) 
continue;

而又因为这道题限定了递增子序列中至少需要有两个元素,所以加一层判定,只要path数组里元素个数>1,就存到result中。

if(path.size()>1){
            result.push_back(path);
        }

总代码如下:

class Solution {
public:
    vector<int>path;
    vector<vector<int>> result;
    void backtracking(vector<int>& nums,int index){
        if(path.size()>1){
            result.push_back(path);
        }
        unordered_set<int> uset;//每一次递归加深层次都会重置这个uset,uset只负责同一树层上的去重
        for(int i=index;i<nums.size();i++){
            if((path.empty()==false&&nums[i]<path.back())||uset.find(nums[i])!=uset.end()) continue;//uset.find()如果找不到相应元素,就会直接返回uset.end(),没返回就说明里面有nums[i]
            path.push_back(nums[i]);
            uset.insert(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums,0);
        return result;
    }
};

有人可能奇怪为什么回溯操作中,我不把uset中的nums[i]元素删掉,因为一个for循环就代表一个树层,我需要判定的就是一个树层中不能出现重复元素,自然不需要跟着回溯删掉。而当递归到更深的树层时,由于我的uset是定义在回溯函数里的,所以会被重置,清空里面所有元素。

46.全排列

 题目链接:46. 全排列 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)

视频链接:组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列

排列题是回溯算法中的经典例题了,首先排列和组合的区别在于,排列中元素的顺序不同,组成的数组就不同,而组合 就算1种,举个例子[1,2,3]和[1,3,2]是不同的排列,却是相同的组合。

首先确认回溯的返回值和参数,返回值就是void,而由于每一次递归,深层遍历都要避免遍历重复的元素,所以参数中除了nums数组外,还要有一个bool类型的数组进行去重。

与之前所做的树层去重不同,此次是树枝去重。

void backtracking(vector<int>& nums,vector<bool>& used)

接着就是确认回溯的终止条件了,很明显,全排列中,当path中的元素个数和原来数组中元素个数相同就意味着排列结束,即终止,将path存入result。

if(path.size()==nums.size()) result.push_back(path);

最后确认单层回溯逻辑:

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

总代码如下:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& nums,vector<bool>& used)
    {
        if(path.size()==nums.size()) result.push_back(path);
        for(int i=0;i<nums.size();i++)
        {
            if(used[i]==true) continue;//用used判断树枝中是否出现了使用过的元素,出现了就跳过
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();
            used[i]=false;
        }
        return;
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

47.全排列 II

 题目链接:46. 全排列 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)

视频链接:组合与排列的区别,回溯算法求解的时候,有何不同?| LeetCode:46.全排列

 这道题其实就是上面两道题的结合体,需要同时使用树层去重和树枝去重。

也就是同时使用到了used和uset,只要上面两道题理解了,这道缝合怪难度就不大了。

当然这道题不使用uset也可以,应为是全排列,每次for循环都是从0一直遍历到最后的,所以也可以用昨天使用的sort再used。

代码如下:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& nums,vector<bool>& used)
    {
        if(path.size()==nums.size()) result.push_back(path);
        unordered_set<int> uset;
        for(int i=0;i<nums.size();i++){
            if((used[i]==true)||(uset.find(nums[i])!=uset.end())) continue;//树枝去重+树层去重
            used[i]=true;
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();
            used[i]=false;
        }
        return;
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

Day29打卡成功,耗时2.5小时,只能说越来越顺手了,很爽!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值