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.全排列
文章链接:代码随想录 (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
文章链接:代码随想录 (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小时,只能说越来越顺手了,很爽!