算法学习记录~2023.5.14~回溯Day3~90.子集II & 491. 递增子序列 & 46.全排列 & 47.全排列 II

文章介绍了如何使用回溯算法解决子集II、递增子序列和全排列II等题目,重点讨论了在有重复元素的情况下如何通过used数组或set进行去重,确保结果的正确性。每个问题都提供了代码实现,包括使用used数组和set两种方法。


90.子集II

题目链接

力扣题目链接

思路

这道题目和 78.子集 区别就是集合里有重复元素了,而且求取的子集要去重。

关于回溯算法中的去重问题,和 40.组合总和II 中的一样,使用一个used数组来进行本树层的去重。
去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了

代码1:使用used数组去重

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    void backtracking (vector<int> nums, int startIndex, vector<bool> used){
        result.push_back(path);
        if (startIndex >= nums.size()){
            return ;
        }
        for (int i = startIndex; i < nums.size(); i++){
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false)   //去重
                continue;
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());     //必须先排序
        backtracking(nums, 0, used);
        return result;
    }
};

代码2:使用set去重

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    void backtracking (vector<int> nums, int startIndex){
        result.push_back(path);
        unordered_set<int> set;     //定义set对同一节点下的本树层去重。注意这个的位置不能在for循环里

        if (startIndex >= nums.size()){
            return ;
        }
        for (int i = startIndex; i < nums.size(); i++){
            if (set.find(nums[i]) != set.end())   //去重,如果能找到则说明本层已经使用过,直接pass
                continue;
            path.push_back(nums[i]);
            set.insert(nums[i]);        //记录元素
            backtracking(nums, i + 1);
            path.pop_back();
            //由于是每层单独定义的set因此并不需要erase掉set
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());     //必须先排序
        backtracking(nums, 0);
        return result;
    }
};

总结


491. 递增子序列

题目链接

力扣题目链接

思路

分为两个问题, 一个是判断递增子序列的问题,另一个是消除重复的子序列。

在这里插入图片描述
在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了。

因此在每一层的循环中,使用 unordered_set 来进行去重,同时因为是每一层定义的,因此不需要回溯。

unordered_set used 是记录本层元素是否重复使用

代码

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    void backtracking (vector<int> nums, int startIndex){
        if (path.size() > 1)
            result.push_back(path);     // 注意这里不要加return,要取树上的节点
        unordered_set<int> used;            //用于记录本层用没用过相同的数
        for (int i = startIndex; i < nums.size(); i ++){    //因为最少两个数
            //不再递增 或 当前树已经在这一层用过
            if ((!path.empty() && nums[i] < path.back()) || used.find(nums[i]) != used.end())
                continue;
            used.insert(nums[i]);           //用了就要标记一下
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums, 0);
        return result;
    }
};

总结

本题使用了unordered_set去重。一开始自己想的时候看例子没能发现重复的问题,采用类似used去重的关键在于想清楚是要同一数层不能重复,这个在写代码时需要注意


46.全排列

题目链接

力扣题目链接

思路

思路其实和组合差不多,只不过由于每次都要从头到尾找,因此不需要startIndex了。

但排列问题需要一个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);
            return ;
        }
        for (int i = 0; i < nums.size(); i++){
            if (used[i] == true)
                continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

总结


47.全排列 II

题目链接

力扣题目链接

思路

这道题目和 46.全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。也就是多了一个去重。

在40.组合总和II 、90.子集II 都涉及了组合问题和子集问题如何去重,放在排列思路也类似。
同样重要的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了

在这里插入图片描述

代码1:使用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);
            return;
        }
        for (int i = 0; i < nums.size(); i++){
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
                continue;
            }
            if (used[i] == false){      //注意这个判断false的条件
                path.push_back(nums[i]);
                used[i] = true;
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

代码2:使用set去重

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        unordered_set<int> uset; // 控制某一节点下的同一层元素不能重复
        for (int i = 0; i < nums.size(); i++) {
            if (uset.find(nums[i]) != uset.end()) {
                continue;
            }
            if (used[i] == false) {
                uset.insert(nums[i]); // 记录元素
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 排序
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

总结

这题看起来和 46.全排列 一样都用了used,但是由于使用了排序,那么代码中的逻辑就可以保证本树层不会重复放入相同元素。

有关这种去重的问题还需要多练习和思考原理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山药泥拌饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值