首页 > 其他分享 >(29/60)递增子序列、全排列、全排列Ⅱ

(29/60)递增子序列、全排列、全排列Ⅱ

时间:2024-02-26 22:13:41浏览次数:28  
标签:排列 nums 复杂度 29 used 60 vector result path

递归子序列

leetcode:491. 非递减子序列

回溯法

思路

类似子集Ⅱ,每个元素大于二的节点都放入结果集。

在填充path的过程中,检查是否满足非严格递增(是否等于末元素或比它大)。

但是由于本题不能排序,之前的去重策略(数组排序,检查nums[i - 1] == nums[i] && used[i - 1] == false)要调整。

设置一个j,向前寻找begin ~ i - 1范围内,是否存在和nums[i]相等的值,如果存在则说明重复,跳过本轮循环。

复杂度分析

时间复杂度:

  • 在 backtracking 函数中,采用了回溯法来找到所有可能的递增子序列,其中使用了循环来遍历数组 nums,时间复杂度为 O(2^n),其中 n 为数组 nums 的长度。
  • 在找到一个递增子序列时,会将其加入到 result 中,这里涉及到复制 path 数组到 result 中,复制的时间复杂度为 O(n),其中 n 为 path 数组的长度。
  • 因此,总体时间复杂度为 O(2^n * n),其中 n 为数组 nums 的长度。

空间复杂度:

  • 使用了 path 和 result 两个额外的数组来保存递增子序列和最终结果,空间复杂度取决于所有递增子序列的数量。最坏情况下,递增子序列的数量为 O(2^n),因此空间复杂度也为 O(2^n)。

注意点

  1. 树层去重的方法:

    1. 数组排序+条件判断i > 0 && nums[i - 1] == nums[i] && used[i - 1]== false

    2. 向前寻找,begin ~ i - 1是否有相等的元素.

      int j = i - 1;
      while(j >= begin) j--;
      if(j >= begin) continue;	// 如果j最后>=begin,说明有相等元素,跳过循环,树层去重
      
  2. 如果用set的话,放入元素的方法叫insert

代码实现

版本一,不用used,向前寻找去重:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
public:
    void backtracking(vector<int>& nums,int begin){
        if(path.size() >= 2){
            result.push_back(path);
        }
        // 树层要去重
        // 对比上一元素,非严格递增才放进去
        for(int i = begin;i < nums.size();i++){
            // 这里没有排序,不能简单比较nums[i - 1]==nums[i]
            // 但可以向前寻找相同元素
            int j = i - 1;
            while(j >= begin && nums[j] != nums[i]) j--;
            if(j >= begin ) continue;    // 如果找到了,那么说明重复,继续
            if(path.size() == 0 || (path.size() > 0 && nums[i] >= path[path.size() - 1 ]) ){
                path.push_back(nums[i]);
                backtracking(nums,i + 1);
                path.pop_back();
            }
        }
    }

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

版本二,每层用set去重:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
public:
    void backtracking(vector<int>& nums,int begin){
        if(path.size() >= 2){
            result.push_back(path);
        }
        // 树层要去重
        // 对比上一元素,非严格递增才放进去
        // set去重版,每层使用过的元素
        unordered_set<int> uset;
        for(int i = begin;i < nums.size();i++){
            if(uset.find(nums[i]) != uset.end() ) continue;
            if(path.empty() ||  nums[i] >= path.back() ){
                uset.insert(nums[i]);     // 放入元素
                path.push_back(nums[i]);
                backtracking(nums,i + 1);
                path.pop_back();
            }
        }
    }

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

版本三,用数组建立哈希映射:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
public:
    void backtracking(vector<int>& nums,int begin){
        if(path.size() >= 2){
            result.push_back(path);
        }
        // 树层要去重
        // 对比上一元素,非严格递增才放进去
        // 数组哈希法
        int used[201] = {0};
        for(int i = begin;i < nums.size();i++){
            if(used[nums[i] + 100] == 1) continue;
            if(path.empty() ||  nums[i] >= path.back() ){
                used[nums[i] + 100] = 1;     // 放入元素
                path.push_back(nums[i]);
                backtracking(nums,i + 1);
                path.pop_back();
            }
        }
    }

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

全排列

leetcode:46. 全排列

回溯法

思想

逻辑是遍历没用过的元素,无所谓顺序,每次都要用完所有元素。

用used数组标记使用过的元素,进行排除。

复杂度分析

时间复杂度:O(N!)

  • backtracking 函数中的 for 循环会遍历 nums 数组,时间复杂度为 O(N),其中 N 是 nums 的长度。
  • 在每次递归调用中,都会将当前元素加入 path 中,然后继续向下递归,因此总体的时间复杂度可以表示为 O(N!),因为每个元素都有 N 种选择,所以共有 N! 种可能的排列方式。

空间复杂度:O(N + N!)

  • path 数组的最大长度取决于输入 nums 数组的长度,因为每个元素都有可能加入或不加入 path 中,所以 path 的空间复杂度为 O(N)。
  • result 数组存储所有符合条件的排列,最坏情况下可能包含 N! 个排列,因此空间复杂度为 O(N!)。
  • used 数组占用了额外的 O(N) 空间。
  • 整体空间复杂度为 O(N + N!)。

注意点

  1. 别把赋值写成==......

代码实现

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
public:
    void backtracking(vector<int>& nums,vector<bool> used){
        if(path.size() == nums.size()){
            result.push_back(path);
            return;
        }
        // 逻辑是遍历没用过的元素,无所谓顺序,每次都要用完所有元素
        for(int i = 0;i < nums.size();i++){
            if(used[i] == true) continue;
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums,used);
            path.pop_back();
            used[i] = false;
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

全排列Ⅱ

leetcode:47. 全排列 II

回溯法

思想

(都打出肌肉记忆了,但其实并没有想的很清楚)

全排列的基础上去重。

去重就是排序+条件判断。

复杂度分析

时间复杂度:

  • n 个元素一共有 n! 中排列方案。
  • 而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组。
  • 最差情况所有元素都是唯一的。复杂度和全排列Ⅰ都是 O(n! * n)

空间复杂度:O(n)

回溯树的深度取决于我们有多少个元素。

注意点

  1. 这题used[i - 1] == false也行而used[i - 1] == true也行。但是一定要加上两者之一,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false。 所以这个条件要写上。

代码实现

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
public:
    // 去重版的全排列
    // 去重 = 排序 + 条件判断
    void backtracking(vector<int>& nums,vector<bool> used){
        if(path.size() == nums.size()){
            result.push_back(path);
            return;
        }

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

标签:排列,nums,复杂度,29,used,60,vector,result,path
From: https://www.cnblogs.com/tazdingo/p/18035697

相关文章

  • NanoFramework操作ESP32(一)_基础元器件篇(三十一)_ MPU6050陀螺仪模块
    一、元器件介绍    MPU-6050是InvenSense公司生产的一款六轴运动处理器,集成了3轴加速度计和3轴陀螺仪;内置的数字运动处理器(DMP)可以实现高级运动处理功能,如六轴运动融合、姿态估计等。这款传感器广泛应用于运动控制和测量领域,如无人机、智能手机、运动手环等。通信接口:I2C(......
  • Python函数每日一讲29 - 一文让你彻底掌握Python中的getattr函数
    引言在Python中,getattr()函数是一种强大的工具,它允许我们在运行时动态地访问对象的属性和方法。本文将介绍getattr()函数的基本语法、常见用法和高级技巧,帮助大家更好地理解和应用这一函数。语句概览getattr()函数的语法如下:getattr(object,name[,default])其中:ob......
  • ThinkPad E560清理风扇积灰
    ThinkPadE560最近风扇嗡嗡响,距离上次清理过去很长时间了,这次准备自己动手清理,上次去店里清理花了100多好像,能省一点是一点,开整在网上买了润滑脂和7921导热硅脂一共加一起来30块钱。润滑脂最后没用上,因为风扇不可拆卸,也没有加润滑脂的口。先拆后盖,螺丝记得放好,我拆开后发现有些......
  • ORA-600 [kclchkblk_4]
    故障描述 某客户一套基于AIX小机的11.2.0.4RAC环境,可能是因为存储故障,该客户对该数据库进行了不完全恢复,具体细节未知,客户在执行alterdatabaseopen阶段报错,数据库无法正常打开。该客户的最终需求是想办法open数据库,他们后续将数据逻辑导出。 故障分析1、让现场的同事上......
  • LG5290/LOJ3052 春节十二响 题解(启发式合并)
    考虑当这个东西是一条链的时候我们该怎么做,显然\(1\)​会有两个儿子,然后两个儿子分别是一条链。所以我们可以给两个儿子的链上的所有节点分别加到两个堆里,每次取出两个堆的最大值加入到我们选择的答案中,然后把两个堆的最大值全部pop掉。最终的答案就是我们pop完一个堆之后,......
  • 7. LCD1602自动巡检方式显示16路温度数据的编程实现
    在多路温度数据,多路压力数据或者多路其它数据采集的过程中,我们借用一个屏幕来显示的时候,一般选用两种模式:自动巡检模式:上电的时候默认的就是这种模式,上节课使用了4个菜单,也就是4个屏,每个屏采集了4路数据,这样的话就相当于让其处于自动巡检的模式,每隔一定的时间来切换一个界面,从而......
  • Leetcode 560 和为k的子数组
    Problem:560.和为K的子数组难点怎么通过前缀和找到和为k的子数组如官方题解所言,[j···i]的子数组=k可转化为pre[i]-pre[j-1]==k要找到前缀和找到和为k的子数组个数就是“找到当前前缀和pre[i]-之前求得的前缀和=k”的总情况。我们通过哈希表记录每个前缀和(的值)出......
  • (27/60)复原IP地址、子集、子集Ⅱ
    阶段性还完了旧账复原IP地址leetcode:93.复原IP地址回溯法思路和分割回文串类似,只是回文串检测改为IP合法检测。终止条件也值得注意:复杂度分析时间复杂度:空间复杂度:注意点代码实现递归不一定要返回!!classSolution{private:vector<string>result;vec......
  • 1.29
    HTML5添加了很多语义元素如下所示:标签描述<article>定义页面独立的内容区域。<aside>定义页面的侧边栏内容。<bdi>允许您设置一段文本,使其脱离其父元素的文本方向设置。<command>定义命令按钮,比如单选按钮、复选框或按钮<details>用于描述文档或文档某个部......
  • Programming Abstractions in C阅读笔记:p293-p302
    《ProgrammingAbstractionsinC》学习第73天,p293-p302总结,总计10页。一、技术总结1.时间复杂度(1)quadratictime(二次时间)p293,AlgorithmslikeselectionsortthatexhibitO(N^2)performancearesaidtoruninquadratictime。2.线性查找(linearsearch)p293,B......