代码随想录 | C++| Day31 | 回溯算法:子集问题

代码随想录 | Day31 | 回溯算法:子集问题

主要学习内容:

1.子集问题

2.复习树层去重

90.子集II

90. 子集 II - 力扣(LeetCode)

解法思路:

这个是数层间去重,不是树枝去重,即如图所示这样

树枝去重我们传入的参数index赋值为i+1而不是i就已经防止了树枝去重,而在树层去重,是在同一层不能选重复的,我们之前说过,一个for循环可以产生一层的结点。现在要在同一层不能选重复的,那说明,在for循环里面选择过的数(比如在i=0时压入了path),在本层函数的for循环其他时候不能压入path。

1.函数参数和返回值

vector<vector<int>> res;
void backtracking(vector<int> path,int index,vector<int> nums,vector<bool> used)

res存放结果

path存放目前放入的数

index为开始的结点,从index开始循环

nums题目数组

2.终止条件

本题是在每个结点都要收集答案,而不是叶子结点,所以终止条件就是for循环结束,不用加别的

3.本层代码逻辑

 // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
 // used[i - 1] == false,说明同一树层false[i - 1]使用过
 // 而我们要对同一树层使用过的元素进行跳过
//所以要再加一个条件就是nums[i]==nums[i-1]
	void backtracking(vector<int> path,int index,vector<int> nums,vector<bool> used)
    {
        res.push_back(path);
        for(int i=index;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(path,i+1,nums,used);
            path.pop_back();
            used[i]=false;
        }
    }

要注意nums一定要是排过序的。

完整代码:

class Solution {
public:
    vector<vector<int>> res;
    void backtracking(vector<int> path,int index,vector<int> nums,vector<bool> used)
    {
        res.push_back(path);
        for(int i=index;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(path,i+1,nums,used);
            path.pop_back();
            used[i]=false;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<int> path;
        vector<bool> used(nums.size(),false);
        sort(nums.begin(),nums.end());
        backtracking(path,0,nums,used);
        return res;
    }
};

哈希表版本:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++) {
            if (uset.find(nums[i]) != uset.end()) {
                continue;
            }
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0);
        return result;
    }
};

491.非递减子序列

491. 非递减子序列 - 力扣(LeetCode)

思路:

其实就是一个子集问题,只不过nums[i]压入path(当前已经收集的数)的时候有了条件,要求是非严格单调递增,那自然而然可以想到nums[i]大于等于path最后一个数才会被压入path,因为path前面的数都是这么压进去的,所以最后一个数肯定是最大的,不用管和前面的数的大小关系

特殊情况,如果path里面没数字,那就直接往里面放数字就完事

1.函数返回值和参数

vector<vector<int>> res;
void backtracking(vector<int> nums,vector<int> path,int index)

res记录结果

path当前已经收集的数

index每层开始遍历的数的下标

2.终止条件

本题是在各个结点都要收集结果,终止条件就是for循环结束条件

因为是子序列,所以至少有两个数才会被压入res结果集

if(path.size()>=2)
	res.push_back(path);

3.本层逻辑

和上面思路所说,第一种情况,path为空,直接往里面放数字就行

			if(path.size()==0)
            {
                path.push_back(nums[i]);
                backtracking(nums,path,i+1);
                path.pop_back();
            }

第二种,只有要放入的数nums[i]大于等于path最后一个数才会被放入

		else if(nums[i]>=path[path.size()-1])
            {
                path.push_back(nums[i]);
                backtracking(nums,path,i+1);
                path.pop_back();
            }

注意:本层的数层去重不能用nums[i-1]=num[i]这种,因为子序列可以不连续,并且原数组也不能排序,所以只能用哈希

if (uset.find(nums[i]) != uset.end())
	continue;
uset.insert(nums[i]);

完整本层逻辑:

		unordered_set<int> uset;
        for(int i=index;i<nums.size();i++)
        {
            if(path.size()==0)
            {
                 if (uset.find(nums[i]) != uset.end())
                    continue;
                uset.insert(nums[i]);
                path.push_back(nums[i]);
                backtracking(nums,path,i+1);
                path.pop_back();
            }
            else if(nums[i]>=path[path.size()-1])
            {
                 if (uset.find(nums[i]) != uset.end()) 
                    continue;
                uset.insert(nums[i]);
                path.push_back(nums[i]);
                backtracking(nums,path,i+1);
                path.pop_back();
            }
        }

可以整合一下代码逻辑:

unordered_set<int> uset;
for(int i=index;i<nums.size();i++)
{
    //非空的时候,小于path最后一个数 或者 重复了,那就跳过进入下一个
	if((!path.empty()&&nums[i]<path.back()) || (uset.find(nums[i])!=uset.end()))
		continue;
	uset.insert(nums[i]);
	path.push_back(nums[i]);
	backtracking(nums,path,i+1);
	path.pop_back();
}

完整代码:

class Solution {
public:
    vector<vector<int>> res;
    void backtracking(vector<int> nums,vector<int> path,int index)
    {
        if(path.size()>=2)
            res.push_back(path);
        unordered_set<int> uset;
        for(int i=index;i<nums.size();i++)
        {
            if((!path.empty()&&nums[i]<path.back()) || (uset.find(nums[i])!=uset.end()))
                continue;
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums,path,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        vector<int> path;
        backtracking(nums,path,0);
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值