【LeetCode回溯算法#08】递增子序列,巩固回溯算法中的去重问题
递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
-
示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]] -
示例 2:
输入:nums = [4,4,3,2,1]
输出:[[4,4]]
说明:
- 给定数组的长度不会超过15。
- 数组中的整数范围是 [-100,100]。
- 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况
思路
题目要找出数组的所有递增子序列,所以需要将整个树结构遍历一遍。
根据说明中的第三点,本题也需要去重。
根据示例2可以看出,我们要找的是数组中当前顺序下的递增序列,因此不能对给定数组进行排序
那就恶心了,要知道我们之前在此类问题中去重考的就是 排序后的相邻重复值 + used标记数组
不管没关系,不能排序也行,那就在单层处理时做手脚
这里还是需要使用used数组来记录单层中使用过的元素
在记录遍历值前,需要做以下判断:
- 使用当前遍历值与之前保存在结果数组path中的值进行大小比较(判定是否有递增趋势)
- 判断当前遍历值是否被记录在used数组中(即之前被使用过)
为了实现上述思路,在回溯时不能删除之前used数组记录的值,且used数组改为在单层递归循环前定义,这样每到一层新的递归层时,used数组都会被清空
代码分析
1、确定回溯函数的参数和返回值
参数时题目给的数组nums、beingIndex,这里used数组在回溯函数里面定义
无返回值
class Solution {
private:
//定义结果数组
vector<vector<int>> res;
vector<int> path;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex){
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
}
};
2、确定终止条件
参考 子集问题 ,如需要收集树结构的所有节点,实际上是不需要返回值的(依靠for循环结束即可)
但是,本题有一个要求是:递增子序列大小至少为2
所以在这里需要单独处理一下这个逻辑(严格来说也不是终止条件)
class Solution {
private:
//定义结果数组
vector<vector<int>> res;
vector<int> path;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex){
//(确定终止条件)
//确保递增子序列大小至少为2
if(path.size() > 2){//2个以上才保存到结果数组
res.push_back(path);
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
}
};
3、确定单层处理逻辑
在单层处理时,还是去循环遍历当前层的元素
不过需要初始化used数组,并且加入当前遍历值与path数组中元素比较大小以及判断当前元素是否被used记录的逻辑
class Solution {
private:
//定义结果数组
vector<vector<int>> res;
vector<int> path;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex){
//(确定终止条件)
//确保递增子序列大小至少为2
if(path.size() > 1){//2个以上才保存到结果数组
res.push_back(path);
}
//确定单层处理逻辑
//初始化used数组,每次进入递归都会被刷新
int used[201] = {0};//题目中说了,数值范围[-100,100],因此直接用数组就行
for(int i = beginIndex; i < nums.size(); ++i){
//判断逻辑
//path是否为空;当前值是否小于path中最新加入的值;或者当前值是否被记录于used
if(!path.empty() && nums[i] < path.back() || used[100 + nums[i]] == 1){
continue;//满足上述条件就跳过
}
used[100 + nums[i]] = 1;//在nums[i]位置记录出现过当前遍历值
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
}
};
注意点
1、vector::back()
用于取数组最新加入的一个值,这里用来取上次添加的值并与当前遍历值进行比较
2、使用数组来记录数的话,需要创建能够容纳所有元素个数的大小。然后只需在该元素大小位置处标记元素是否出现过即可
例如:如果遍历值是2,那么应该在used数组的100 + 2,也就是下标为102处记录1,表示2出现过1次
加100是因为题目所给范围是[-100,100],前100是负数值
完整代码
class Solution {
private:
//定义结果数组
vector<vector<int>> res;
vector<int> path;
//确定回溯函数的参数和返回值
void backtracking(vector<int>& nums, int beginIndex){
//(确定终止条件)
//确保递增子序列大小至少为2
if(path.size() > 1){//2个以上才保存到结果数组
res.push_back(path);
}
//确定单层处理逻辑
//初始化used数组,每次进入递归都会被刷新
int used[201] = {0};//题目中说了,数值范围[-100,100],因此直接用数组就行
for(int i = beginIndex; i < nums.size(); ++i){
//判断逻辑
//path是否为空;当前值是否小于path中最新加入的值;或者当前值是否被记录于used
if(!path.empty() && nums[i] < path.back() || used[100 + nums[i]] == 1){
continue;//满足上述条件就跳过
}
used[100 + nums[i]] = 1;//在nums[i]位置记录出现过当前遍历值
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0);
return res;
}
};