Day 29 491.递增子序列46.全排列 47.全排列 II

文章讨论了在给定整数数组中寻找递增子序列的方法,强调了与之前全排列问题的区别,即在原数组上寻找子序列无需排序。文章还介绍了全排列问题的不同版本,包括无重复数字、可包含重复数字的情况,以及对应的去重策略和代码实现。
摘要由CSDN通过智能技术生成

递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

​ 和之前的问题看上去很像,得到非重复递增子集,但是要注意区别和之前的差异:

​ 在操作子集Ⅱ的问题的时候, 是先对原数组进去排序,再进行回溯操作;

​ 而此处要求是在原数组中寻找递增子序列,所以不能改变数组元素的位置,用和之前一致的操作方法去进行回溯;因为排序之后可能得到原数组中不存在的递增子序列;

​ 以[4,7,6,7]为例:(所有的回溯算法都是深度优先搜索)

	vector<int>	path;
	vector<vector<int>>	res;
	void backtracking(vector<int>& nums, int startIndex){
        if(path.size() >= 2){
            res.push_back(path);
            //return;//本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归
        }
        unordered_set<int>	uset;
        for(int i = startIndex; i < nums.size(); i++){
            if((!path.empty() && nums[i] < path.back()) //判断子序列是否递增,非递增则进入下层循环
               || uset.find(nums[i]) != uset.end()){//找到元素是否使用过,使用过则进入下层循环
                continue;//完成去重操作
            }
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

​ 整体代码如下:

class Solution {
private:
	vector<int>	path;
	vector<vector<int>>	res;
	void backtracking(vector<int>& nums, int startIndex){
        if(path.size() >= 2){
            res.push_back(path);
            //return;//本题其实类似求子集问题,也是要遍历树形结构找每一个节点,可以不加终止条件,startIndex每次都会加1,并不会无限递归
        }
        //unordered_set<int>	uset;	可以用数组来实现哈希
        int used[201] = {0};
        for(int i = startIndex; i < nums.size(); i++){
            if((!path.empty() && nums[i] < path.back()) //判断子序列是否递增,非递增则进入下层循环
               //|| uset.find(nums[i]) != uset.end()){//找到元素是否使用过,使用过则进入下层循环
               || used[nums[i] + 100] == 1){//因为数组范围为[-100,100];
                continue;//完成去重操作
            }
            //uset.insert(nums[i]);
    		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) {
        path.clear();
        res.clear();
        backtracking(nums, 0);
        return res;
    }
};

全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]
  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

​ 由于没有重复数字,所以无须去重;

​ 排列问题其实本质和组合问题的处理方法本质来说是一致的,只是舍弃的节点处理方式有所区别;

​ 排列问题是用used数组树枝去重;

​ 组合问题无重复元素用startIndex去重,有重复元素做数层去重(used数组或者hashset);

​ 如下图所示:

​ used数组来记录是否使用过此元素,然后删除此条树枝即可;

​ 代码如下:

    vector<vector<int>>	res;
	vector<int>	path;
	//主函数里定义used	vector<bool> used(nums.size(), false);
	void backtracking(vector<int>& nums, vector<bool>& used){
        if(path.size() == nums.size()){
            res.push_back(path);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(used[i] == true)	continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }

​ 整体代码如下:

class Solution {
private:
    vector<vector<int>>	res;
	vector<int>	path;
	void backtracking(vector<int>& nums, vector<bool>& used){
        if(path.size() == nums.size()){
            res.push_back(path);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(used[i] == true)	continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        res.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return res;
    }
};

全排列Ⅱ

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

  • 输入:nums = [1,1,2]
  • 输出: [[1,1,2], [1,2,1], [2,1,1]]

示例 2:

  • 输入:nums = [1,2,3]
  • 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8

  • -10 <= nums[i] <= 10

    ​ 很显然,需要去重了;首先是对数组进行排序,然后再分析树形结构;

​ 很显然,这里用used数组去重即可;

	if(i > 0 &&nums[i] == nums[i - 1] && used[i-1] == false)	continue;

​ 整体代码如下:

class Solution {
private:
    vector<vector<int>>	res;
	vector<int>	path;
	void backtracking(vector<int>& nums, vector<bool>& used){
        if(path.size() == nums.size()){
            res.push_back(path);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false)	continue;
           if(used[i] == false){
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        res.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());
        backtracking(nums, used);
        return res;
    }
};

​ 这里还有个神奇的事,就是used[i-1] == true也可以实现;

​ 以下图为例:

​ 首先是used[i] == false;的判断条件进行判断(树层去重)

​ 然后是used[i] == true;判断条件的遍历逻辑(树枝去重)

​ 很显然树层去重效率更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值