昨天偷懒了,今天补打卡
力扣题部分:
491.递增子序列
题目链接:. - 力扣(LeetCode)
题面:
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
思路:
这道题第一个坑点是nums = [4,6,7,7]时[4,7,7]也算一个子序列,也就是说,子序列可以取非连续的数组元素,没注意到这个可能代码全部都得重新考虑了。
这又是子集,又是去重,是不是不由自主的想起了刚刚讲过两遍(今天除了本题下面还有第三遍)的去重方法,但是很遗憾,方法要对原数组排序,**而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以本题看起来和别的题目差不了多少,但是一细想会发现陷阱真的蛮多的。**那具体怎么解决这里的去重呢?我们还是用回溯三部曲说明去重的思路和部分代码好了。
回溯三部曲
递归函数参数
cpp
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex)
这环节倒没啥不一样的,两个全局变量,因为也算是求子集,所有startIndex也是必要的。
终止条件
本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归。
但本题收集结果有所不同,题目要求递增子序列大小至少为2,所以代码如下:
注意这里不要加return,因为要取树上的所有节点。
cpp
if (path.size() > 1) {
result.push_back(path);
}
关于树形结构,具体的图在下面可以看到。
在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了
cpp
unordered_set<int> uset; // 使用set来对本层元素进行去重
for (int i = startIndex; 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, i + 1);
path.pop_back();
}
代码通过每层内创建一个**unordered_set<int> uset
** 记录已经用过的元素,因为是每层重新创建,和之前回溯老套路不一样的是不需要对**unordered_set<int> uset
** 使用类似pop的操作,因为unordered_set<int> uset
是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!
代码实现:
cpp
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, int startIndex)
{
if(path.size() > 1) result.push_back(path);
unordered_set<int>set;
for(int i = startIndex; i < nums.size(); i ++)
{
if((path.size() && path[path.size() - 1] > nums[i]) || set.find(nums[i]) != set.end())
continue;
set.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;
}
};
46.全排列
题目链接:. - 力扣(LeetCode)
题面:
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
思路:
在前面的回溯学习中,我们接触了组合,子集 和分割 三种回溯可以解决的问题类型,这是我一刷代码随想录涉及到的最后一种类型了:排列。
相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。
所以正如我们在之前所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?
因为一些问题能暴力搜出来就已经很不错了!
下面是回溯三部曲
递归函数参数
两个全局变量还是一样的,一个用来记录符合条件的一个排列(rightnums),另一个负责记录所有的排列(results)。
不一样的是为了保证不会重复选取数字,加入了一个bool数组used记录。
递归终止条件
如果每个元素都只拿一次,很显然当记录排列的rightnums数组和nums长度相同的时候,我们的排列就可以放进results里了
单层搜索的逻辑
每次i从0开始选取的前提是这个元素之前没被选过,一经选取use[i]就要变成true,其他的就没什么了,和之前的for循环基本一致。
代码实现:
cpp
class Solution {
public:
vector<int>rightnums;
vector<vector<int>>results;
void find(vector<int>nums, vector<bool> used)
{
if(rightnums.size() == nums.size())
{
results.push_back(rightnums);
return;
}
for(int i = 0; i < nums.size(); i ++)
{
if(used[i] == false)
{
rightnums.push_back(nums[i]);
used[i] = true;
find(nums, used);
rightnums.pop_back();
used[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<bool> used(nums.size(),false);
find(nums, used);
return results;
}
};
47.全排列 II
题目链接:. - 力扣(LeetCode)
题面:
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
思路:
和上一道题唯一的区别就是去重 的问题**,但是好消息是全排列对数组顺序没有要求,也就是说我们可以通过排序让重复的元素放在一起....**想起来没?这么熟悉的去重操作,相信如果你看过之前的回溯二,回溯三的内容的话,应该知道该怎么去重了吧?一个比较抽象的去重逻辑,这已经是第三次出现了,我还是把这个逻辑的解释的文章传送门放这里,看不懂下面这行代码的可以去我原来的文章里看看这么解释的。
传送门(第二道题目 组合总和|| ):代码随想录算法训练营第二十三天(回溯 二)-CSDN博客
if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
相信明白了去重之后下面的的代码不难打出:
代码实现:
cpp
class Solution {
public:
vector<vector<int>> results;
vector<int>rightsum;
void find(vector<int> nums, vector<bool> used)
{
if(nums.size() == rightsum.size())
{
results.push_back(rightsum);
return;
}
for(int i = 0; i < nums.size(); i ++)
{
if(i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
if(used[i] == false)
{
used[i] = true;
rightsum.push_back(nums[i]);
find(nums, used);
used[i] = false;
rightsum.pop_back();
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(),false);
find(nums, used);
return results;
}
};