代码随想录算法训练营第29天 | 491.非递减子序列, 46.全排列, 47.全排列II

Leetcode - 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]]

笔记:

这道题乍一看跟上个题目很像,但是由于让你求的时非递减子序列,所以只能再远数组的基础上进行去重不能在排序了。这时候我们就要用到特定的容器来进行去重了。

这里我们需要捋清楚思路了:我们去重的逻辑是:去除掉同一树层中开头元素相同的组合。所以我们进需要在for循环的下面进行去重即可:这个理我们去重可以采用set容器也可以使用哈希表进行标记。

(1)set容器去重:

这里我们需要去重的逻辑是在set容器中是否能够找到当前元素:

if(myset.find(s[i]) != myset.end())

 如果找到了就跳过当前元素,当我们发现当前元素上一个元素小时也跳过:

if(!path.empty() && nums[i] < path.back() || myset.find(nums[i]) != myset.end())

class Solution {
public:
    vector<bool> used;
    vector<int> path;
    vector<vector<int>> res;
    void solve(const vector<int>& s, int start){
        if(path.size() > 1){
            res.push_back(path);
        }
        unordered_set<int> myset;
        for(int i = start; i < s.size(); i++){
            if(!path.empty() && path.back() > s[i] || myset.find(s[i]) != myset.end()){
                continue;
            }
            path.push_back(s[i]);
            myset.insert(s[i]);
            solve(s, i + 1);
            path.pop_back();
            // 在同一树枝中当遇到第二个 与前面相同的元素时set容器中只加入了一个,而删除的时候删除了两个
            // 因为是同一层去重所以set需要在本层循环的上一步进行创建,在每一次创建时会更新set的数据
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        solve(nums, 0);
        return res;
    }
};

这道题还留有一点疑问:如果在外部创建myset的话为什么不行呢。我觉得应该是在外层的话就是set内部管理所有的元素并不单单知识负责本层的数据了。

(2)使用哈希表,利用used数组:

这道题的重点在于要标记当前元素防止同层元素再次使用,重点在于对于同一树层中的元素进行标记。

class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;
    void solve(const vector<int>& s, int start){
        if(path.size() > 1){
            res.push_back(path);
        }
        vector<bool> used(205, false);
        for(int i = start; i < s.size(); i++){
            if(!path.empty() && s[i] < path.back() || used[s[i] + 100] == true){
                continue;
            }
            used[s[i] + 100] = true;
            path.push_back(s[i]);
            solve(s, i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        solve(nums, 0);
        return res;
    }
};

 Leetcode - 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]]

笔记: 

这道题重要的是把思路理清楚,因为这道题是个排列题目,所以我直接就能想到与之前提不一样的地方就是在for循环时要将i从0开始遍历,不再需要start来标记遍历到的位置。但是怎么控制每次便利的时候会略过上一层遍历的元素这又是一个问题。所以我们需要一个used数组来记录当前树枝依次走过的元素。接下来回溯三部曲:

(1)确定参数:我们这里需要传入的参数是原数组与used数组、

(2)确定终止条件:这里我们需要找到叶子结点才可以返回,而叶子结点的标志就是path数组的大小等于原数组的大小,就代表着所有元素都找到了位置。

(3)确定单层循环逻辑:这里我们从0开始遍历,如果遇到了上层遍历过得就直接跳过,若果没有就将该元素加入path数组接着递归然后回溯used数组的状态并弹出元素。

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

Leetcode - 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]]

笔记: 

去重问题第一步:对数组进行排序,将重复元素归拢。第二步:判断是否去重,当前一个元素与当前元素一样时,判断前一个元素的used数组内是什么状态,如果是false则代表同一树层已遍历过上一个元素就直接跳过因为已经遍历并回溯了。如果为true则代表在同一树枝使用过上一个元素,可以继续使用。

但这里并不只是丹丹的去重,和之前组合的去重不一样的是组合去重并不是从0开始遍历的。排列从0开始遍历,需要我们判断当前是否跳过该元素的情况是:当前元素已经使用过以及上一个元素出现在同意树层。也就是在组合的去重上要再加上对当前元素状态的判断:

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

注意事项:

在第一道题非递减子序列中,我们用了used数组来作为哈希表进行去重,第四题同样使用了used数组来去重,单着两个数组的定义位置大不相同,下面是第一道题的思维导图:

下面是第二道题的思维图:

分析这两个图的不同,第一道题是无序的,第二道题是有序的 ,但是两者的去重思路不一样:

(1)第一道题的去重思路是对同一根节点的树层去重,也就是在遇到一个树层时,我们就创建一个新的used数组,如果后面的元素与之前的元素重了我们就直接跳过。因为是无序的所以我们就只能没遇到一个树层就进行创建used数组。for循环只会在同一根节点的树层内进行。因为遇到一个树层就更新所以也不需要回溯。

(2)第二道题的去重思路是:因为数组是有序的,所以我们只需要判断如果遇到了连续好几个一样的元素,我们used是用来区别不同的情况:一种是数枝遍历,可以使用数组内相同的元素,一种是树层遍历,不可以使用相同的元素。但我们要怎么来却区别两种情况呢?我们会对used数组进行回溯,在一个数值遍历完成之后其used状态也都恢复为fasle了,而如果我们便两道相同元素,当我们遍历到第二个元素,会检查前一个的used状态,如果是true则代表是统一树枝,如果是false则代表是同意树层。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练中的讨论内容常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值