代码随想录第二十五天 | Leecode 491. 非递减子序列、46. 全排列、47. 全排列 II

Leecode 491. 非递减子序列

题目描述

给你一个整数数组 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]]

解题思路与代码

本题和之前的有重复元素的子集那道题的回溯有点相似,都是其中会有一些重复的元素,而有重复的元素就要涉及到去重的操作。在那道子集的题目中,去重是通过先对集合中元素进行排序,随后再在回溯中使用start变量的条件判断来进行去重。但本题不同的点在于,要求输出的是原序列中非递减的一部分子序列,这意味着我们需要保持原本的排序关系,不能再先对原本序列进行排序操作。从而也就无法用之前那样的方式来进行去重了。本题去重考虑使用unordered_set容器来记录每一层中的变量是否已经在回溯递归中使用过。可以写出如下代码:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> curVec;

    void backTracking(const vector<int>& nums, int start){
        if(curVec.size() >= 2){  // 只要长度大于等于2的子序列都进行存储
            result.push_back(curVec);
        }
        unordered_set<int> used; // 用于记录已经用过的元素
        for(int i = start; i < nums.size(); i++){
            if(!curVec.empty() && nums[i] < curVec.back()) continue; // 如果当前元素比子序列中最后一个元素小,则跳过
            if(used.find(nums[i]) != used.end()) continue; // 如果子序列中已经用过当前元素,则跳过
            curVec.push_back(nums[i]); 
            used.insert(nums[i]);
            backTracking(nums, i + 1);
            curVec.pop_back();
        }
    }

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backTracking(nums, 0);
        return result;
    }
};

Leecode 46. 全排列

题目描述

给定一个不含重复数字的数组 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]]

解题思路与代码展示

本题要求的是数组中数字的全排列,排列与之前做过的组合的区别在于排列需要关注每个元素出现的顺序,而组合中只需要关注出现了哪些元素。既然是全排列,那么使用回溯法过程中,进行存放结果的条件判断应该非常简单,只需要当前数组中的元素个数等于原数组中的个数即可。但关键在于我们需要如何判断回溯过程中,每一个元素是否已经出现在过当前数组里。因此我们考虑使用一个存放布尔类型的vector来存放某个元素是否被使用过。由此我们可以写出下面代码:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> curVec;

    void backTracking(vector<int>& nums, vector<bool> used){
        if(curVec.size() == nums.size()){ // 当前数组中的元素数量和原数组元素数量相等,则存放结果
            result.push_back(curVec);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(used[i]) continue; // 如果当前数已经用过,则直接跳过
            curVec.push_back(nums[i]); // 没用过,则将其放到当前数组中
            used[i] = true; // 更新used向量
            backTracking(nums, used); // 递归
            used[i] = false; // 回溯
            curVec.pop_back(); // 回溯
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        curVec.clear();
        vector<bool> used(nums.size(), false); // 使用used向量来存放每个元素是否有被使用过
        backTracking(nums, used);
        return result;
    }
};

47. 全排列 II

题目描述

给定一个可包含重复数字的序列 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]]

解题思路与代码展示

本题与上一题比较相似,区别在于原始数组中可能出现重复元素。如果使用上面的代码会产生重复。为了去重可以考虑使用set来存放结果,并在最后将set转换为vector即可。

class Solution {
public:
    set<vector<int>> result; // 使用set来存放结果
    vector<int> curVec;

    void backTrack(vector<int>& nums, vector<bool> used){
        if(curVec.size() == nums.size()){
            result.insert(curVec);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(used[i]) continue;
            used[i] = true;
            curVec.push_back(nums[i]);
            backTrack(nums, used);
            curVec.pop_back();
            used[i] = false;
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        curVec.clear();
        vector<bool> used(nums.size(), false);
        backTrack(nums, used);
        vector<vector<int>> re(result.begin(), result.end()); // 将set转换为vector
        return re;
    }
};

上面代码中的逻辑和上题几乎完全一致,区别仅在于使用了set容器来进行去重。

posted on 2025-04-21 18:52  JQ_Luke  阅读(295)  评论(0)    收藏  举报