前言:
如果你一点也不了解什么叫做回溯算法,那么推荐你看看这一篇回溯入门,让你快速了解回溯算法的基本原理及框架
491.递增子序列
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
来源:力扣(LeetCode)
思路
由题意可得下面两点要求:
- 递增的子序列,且元素数量大于2
- 子序列与子序列不能相同
- 可使用重复出现的数字
像这种需要依次取元素,然后将元素存储起来汇成总集合,期间可能还需要回退取不一样集合的题目,我们第一个想到的可以使用回溯法,那么该如何回溯呢?且看下图分析:我们使用[ 4,7,6,7 ]举例
过程总结:
- 回溯收集子集条件
根据题意可以得知,我们只要子序列的数量大于等于2就可以 - 回溯终止条件
终止条件就是达到nums.size() - 单层搜索逻辑
我们由图可以得知,虽然序列里面可能有重复的数字,但是单层我们不能取相同的数字,如果我们取了相同的数字,那么必定会存在相同的子集,所以我们单层需要去重
单层去重我们这里使用一个标记的容器 unordered_set<int> use
存放已经出现过的数字
参考代码:
class Solution {
public:
vector<vector<int>> arr;
vector<int> _arr;
void BackTracking(vector<int>& nums,int begin)
{
if(_arr.size()>=2)//只要数据>=2就存储,我们这里不需要return
{
arr.push_back(_arr);
}
unordered_set<int> use;//标记容器
for(int i=begin;i<nums.size();i++)
{
//如果是空直接存放,然后判断别的关系
if((!_arr.empty() && _arr.back()>nums[i]) || use.find(nums[i])!=use.end())
{
continue;
}
_arr.push_back(nums[i]);
use.insert(nums[i]);
BackTracking(nums,i+1);//不能重复使用单个数据,所以我们需要i+1
_arr.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
BackTracking(nums,0);
return arr;
}
};
优化
题目中说了,数值范围[-100,100],所以完全可以用数组来做哈希。
程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。
优化代码:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
if (path.size() > 1) {
result.push_back(path);
}
int used[201] = {0}; // 这里使用数组来进行去重操作,题目说数值范围[-100, 100]
for (int i = startIndex; i < nums.size(); i++) {
if ((!path.empty() && nums[i] < path.back())
|| used[nums[i] + 100] == 1) {
continue;
}
used[nums[i] + 100] = 1; // 记录这个元素在本层用过了,本层后面不能再用了
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
47.全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
来源:力扣(LeetCode)
本题是全排列的进阶版,之前是没有重复元素,现在有重复元素,我们该如何解决呢?
思路
这个题与上一题递增子序列相差不多,也是需要单层去重,且看下图:
相较于之前的收集元素,排列我们需要将每个元素都使用到,所以我们每次循环开始条件都为0,但是为了不使用一个使用过的元素,我们需要设置一个标记的数组,使用过一个标记一个,单层去重,是因为同一层使用相同的元素没有意义,使用相同元素,相当于递归两遍相同数据,导致出现相同子集
过程总结:
- 先排序所有元素
- 标记数组
- 单层去重
参考代码:
class Solution {
public:
vector<vector<int>> arr;
vector<int> _arr;
void BackTracking(vector<int>& nums,vector<bool>& use)
{
if(_arr.size()==nums.size())
{
arr.push_back(_arr);
return;
}
for(int i=0;i<nums.size();i++)
{
//单层去重,及判断元素是否使用过
if(i>0 && nums[i]==nums[i-1] && use[i-1]==false)
{
continue;
}
if(use[i]==false)
{
use[i]=true;
_arr.push_back(nums[i]);
BackTracking(nums,use);
_arr.pop_back();
use[i]=false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(),nums.end());//需要排序,为去重做准备
vector<bool> use(nums.size(),false);
BackTracking(nums,use);
return arr;
}
};
如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀