【LeetCode回溯算法#08】递增子序列,巩固回溯算法中的去重问题

发布时间 2023-03-22 19:13:33作者: dayceng

递增子序列

力扣题目链接(opens new window)

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是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;
    }
};