面试热点题:回溯算法 递增子序列与全排列 II

前言:
如果你一点也不了解什么叫做回溯算法,那么推荐你看看这一篇回溯入门,让你快速了解回溯算法的基本原理及框架

491.递增子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

来源:力扣(LeetCode)
在这里插入图片描述

思路

由题意可得下面两点要求:

  • 递增的子序列,且元素数量大于2
  • 子序列与子序列不能相同
  • 可使用重复出现的数字

像这种需要依次取元素,然后将元素存储起来汇成总集合,期间可能还需要回退取不一样集合的题目,我们第一个想到的可以使用回溯法,那么该如何回溯呢?且看下图分析:我们使用[ 4,7,6,7 ]举例
在这里插入图片描述
在这里插入图片描述
过程总结:

  1. 回溯收集子集条件
    根据题意可以得知,我们只要子序列的数量大于等于2就可以
  2. 回溯终止条件
    终止条件就是达到nums.size()
  3. 单层搜索逻辑
    我们由图可以得知,虽然序列里面可能有重复的数字,但是单层我们不能取相同的数字,如果我们取了相同的数字,那么必定会存在相同的子集,所以我们单层需要去重

单层去重我们这里使用一个标记的容器 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;
    }
};

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侠客cheems

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值