【代码随想录训练营第42期 Day25打卡 回溯Part4 - LeetCode 491.非递减子序列 46.全排列 47.全排列 II

目录

一、做题心得

二、题目与题解

题目一:491.非递减子序列

题目链接

 题解:回溯+哈希

题目二:46.全排列 

题目链接

题解1:排序+调用库函数

题解2:回溯

题目三:47.全排列 II

题目链接

题解1:排序+调用库函数

题解二:回溯

三、小结


一、做题心得

今天的题目,个人感觉还是有点麻烦的。首先就是递增子序列,这个题的难点我感觉就是如何实现去重以及怎么实现递增子序列;然后后边两道是排列问题,排列问题是要讲究顺序的,和前边的组合问题有着本质上的区别,这里我们之前一直硬套的模板就得响应改变下。

话不多说,直接开始今天内容。

二、题目与题解

题目一:491.非递减子序列

题目链接

491. 非递减子序列 - 力扣(LeetCode)

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 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]]

提示:

  • 1 <= nums.length <= 15
  • -100 <= nums[i] <= 10
 题解:回溯+哈希

这道题的话,是我们的子序列问题,很明显,这道题就是需要枚举出所有满足非递减的子序列,枚举的话,首先就想到回溯能否解决这个问题。

这个题的关键,我认为有两点:

一.怎样保证子数列是非递减子数列

这里我想到了两种解决方案:

第一种就是自定义一个函数,然后每个最后得到的子序列代入判断,如下:

bool isRise(vector<int> v) {
        for (int i = 1; i < v.size(); i++) {        
            if (v[i] < v[i - 1]) {
                return false;
            }
        }
        return true;
    }

第二种就是在每次循环遍历的时候加上条件语句判断后一个将要添加的数是否比已经添加的最后一个数小(这是因为数组的顺序是固定的,我们每次添加都是按顺序添加nums的元素,我们只需要确定每次添加元素比上一个大即可),即:

!vec.empty() && nums[i] < vec.back()

二.怎样实现去重

这里我们采用哈希+横向去重解决组合之间重复的情况

横向去重(hash.count(nums[i])):已经在本路径中使用过,则跳过。选择横向去重就是解决组合之间存在的相同情况--这个在之前打卡中提到过,就不多说了。

 整体代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> vec;
    void backtrack(vector<int>& nums, int start) {      //回溯--也可以理解为dfs
        if (vec.size() >= 2) {       //满足条件:当前非递减子序列至少2个元素
            ans.push_back(vec);
        }
        unordered_set<int> hash;            //建立哈希表--实现去重
        for (int i = start; i < nums.size(); i++) {
            if (!vec.empty() && nums[i] < vec.back() || hash.count(nums[i]) ) {     //1.确保得到的序列都是非递减子序列(!vec.empty() && nums[i] < vec.back())      2.横向去重(hash.count(nums[i])):已经在本路径中使用过,则跳过(确保本层元素不重复)  
                    continue;
            }
            hash.insert(nums[i]);           //记录这个元素在本层用过了,本层后面不能再用了
            vec.push_back(nums[i]);
            backtrack(nums,i + 1);
            vec.pop_back();
        }        
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        if (nums.size() < 2) {
            return ans;
        }
        backtrack(nums, 0);
        return ans;
    }
};

题目二:46.全排列 

题目链接

46. 全排列 - 力扣(LeetCode)

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同
题解1:排序+调用库函数

这个也没什么特别需要说的,直接记忆即可,专门用来解决全排列问题的库函数。唯一需要注意的就是一定要先对数组排序。

代码如下:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>>ans;
        sort(nums.begin(), nums.end());     //注意要先对数组排序
        do
        {
            ans.push_back(nums);
        }while(next_permutation(nums.begin(),nums.end()));
        return ans;
    }
};
题解2:回溯

回溯经典问题之排列问题

这里需要注意的就是需要自定义visited数组(bool型),并初始化大小为nums,size(),以及全为false。visited数组的作用就是标记并判断当前元素是否被访问过,从而每次排列的时候避开以访问过的元素并新标记刚访问的元素,这样就能不重复的得到所有排列的元素集合--注意这里不同顺序的相同组合算不同排列。(注意for循环是从i = 0开始,递归是进行同样的递归--不过此时visited已经有做出相应改变了)。

代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> vec;
    void backtrack(vector<int>& nums, vector<bool>& visited) {
        if (vec.size() == nums.size()) {
            ans.push_back(vec);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {         //循环遍历,从数组每个不同下标的元素开始,记录排列
            if (visited[i] == true) {       //如果当前元素已经被访问过,则跳过
                continue;
            }
            visited[i] = true;          //若没被访问过,这时标记当前元素为已访问并进行后续操作
            vec.push_back(nums[i]);
            backtrack(nums,visited);
            vec.pop_back();             //回溯,撤销当前元素
            visited[i] = false;       //撤销后,标记当前元素为未访问
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> visited(nums.size(), false);       //初始化visited数组:用来标记当前排列中的当前元素是否被访问(即是否在排列中),大小与nums相同,且全为false(表示都还没访问过)
        backtrack(nums, visited);
        return ans;
    }
};

题目三:47.全排列 II

题目链接

47. 全排列 II - 力扣(LeetCode)

给定一个可包含重复数字的序列 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
题解1:排序+调用库函数

和上边的题一样,都可以直接使用库函数--相当于是所有求全排列的通式。

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>>ans;
        sort(nums.begin(), nums.end());     //注意要先对数组排序
        do
        {
            ans.push_back(nums);
        }while(next_permutation(nums.begin(),nums.end()));
        return ans;
    }
};
题解二:回溯

这个题作为上边那题的进阶版,整体思路是一样的,这里要考虑的如何实现去重,即如何实现除去重复的排列。

思路也不难,大概做法就和组合问题去重差不多:在去重前先排序,然后再比较相邻元素是否相同。不过这里需要注意的是,这里同时需要保证同一树层nums[i-1]使用过才跳过即visited[i - 1] == false。

代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> vec;
    void backtrack(vector<int>& nums, vector<bool>& visited) {
        if (vec.size() == nums.size()) {        //终止条件:全排列--全部元素都排列
            ans.push_back(vec);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == false) {       //横向去重:如果同一树层nums[i - 1]使用过则直接跳过。注意这里visited[i-1]==false(同层剪枝)或者visited[i-1]==true(非同层剪枝)都行
                continue;
            }
            if (visited[i] == true) {       //如果当前元素已经被访问过,则跳过
                continue;
            }
            visited[i] = true;          //若没被访问过,标记当前元素为已访问
            vec.push_back(nums[i]);
            backtrack(nums,visited);
            vec.pop_back();             //回溯,撤销当前元素
            visited[i] = false;       //标记当前元素为未访问
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> visited(nums.size(), false);
        sort(nums.begin(), nums.end());         //去重前先排序
        backtrack(nums,visited);
        return ans;
    }
};

三、小结

今天的题感觉和前几天又有一点不同了,排序的做法和组合的做法区别还是比较大,做题时还是得注意区分。好了,今天的打卡到此结束。最后,我是算法小白,但也希望终有所获。

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值