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


    递增子序列

    力扣题目链接(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;
        }
    };
    
  • 相关阅读:
    urdf标签理解
    .net+bootstrap写的一个还不错的音乐网站
    Android的PendingIntent.getBroadcast()PendingIntent.FLAG_IMMUTABLE问题
    【Spring Cloud实战】Spring Cloud Alibaba Sentinel熔断与限流 (最全讲解,附源码)
    网络简答题带答案
    好奇心驱使下试验了 chatGPT 的 js 代码的能力
    【ESP 保姆级教程】疯狂Node.js服务器篇 ——钉钉/微信/飞书报警从Arduino移植到NodeJs服务器实现
    【JUC系列-08】深入理解CyclicBarrier底层原理和基本使用
    第二十二章 alibaba sentinel详解-各种规则的应用
    谷粒商城错误总结
  • 原文地址:https://www.cnblogs.com/DAYceng/p/17207384.html