• 300. 最长递增子序列 ●●


    300. 最长递增子序列 ●●

    描述

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度

    子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

    示例

    输入:nums = [10,9,2,5,3,7,101,18]
    输出:4
    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

    题解

    1. 动态规划

    1. dp[i] 表示以第 i 个数字nums[i]为结尾的最长递增序列长度;
    2. if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
      位置 i 的最长升序子序列等于j从0到 i-1各个位置的最长升序子序列 + 1 的最大值。
      注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值
    3. 每一个 i,对应的dp[i](即最长上升子序列)起始大小至少都是1;
    4. 双层循环,从前往后遍历。
    • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n 为数组 nums 的长度。动态规划的状态数为 n,计算状态 dp[i] 时,需要 O(n) 的时间遍历 dp[0…i−1] 的所有状态。
    • 空间复杂度:O(n),需要额外使用长度为 n 的 dp 数组。

    在这里插入图片描述

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            int n = nums.size();
            int ans = 1;
            vector<int> dp(n, 1);   	// dp[i] 表示以第i个数字为结尾的最长递增序列长度
            for(int i = 1; i < n; ++i){	// nums[i]结尾
                for(int j = 0; j < i; ++j){
                    if(nums[i] > nums[j]){
                        dp[i] = max(dp[i], dp[j] + 1);	// 升序时,0到i-1各个位置的最长升序子序列 + 1
                    }
                }  
                ans = max(ans, dp[i]);	// 取最大值
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. DP + 二分查找

    通过重新设计状态定义,使整个 dp 为一个排序列表;这样在计算每个 dp[k] 时,就可以通过二分法遍历 [0,k) 区间元素,将此部分复杂度由 O(N) 降至 O(logN)。

    定义 dp[i] 表示最长递增序列长度为 i+1 的最小末尾数,如 {1, 2, 4, 3} 中,最长递增序列长度为 3 的最小末尾数 dp[2] = 3;

    此时 dp 数组一定是严格递增的,即 k < n 时有dp[ k ] < dp[ n ],当尽可能使每个子序列尾部元素值最小的前提下,子序列越长,其序列尾部元素值一定更大。(可用反证法证明)。

    步骤为:

    1. 新建dp数组,用于存放最长递增序列长度为 i+1 的最小末尾数,初始化dp[0] = nums[0];
    2. 遍历数组nums,对每个元素进行比较插入操作,
      1)如果dp中元素都小于它,则尾部插入新元素;
      2)否则,用二分法找到第一个大于等于 nums[i] 的位置,进行替换。

    总之,思想就是让 dp[i] 存储序列长度为 i+1 的最小末尾数。这样,dp 未必是真实的最长上升子序列,但其长度就是最终的子序列长度。 比如 {1, 2, 9, 10, 3, 4, 5} 的替换过程。

    • 时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN) : 遍历 nums 列表需 O(N),在每个 nums[i] 二分法需 O(logN)。
    • 空间复杂度 O(N) : tails 列表占用线性大小额外空间。

    在这里插入图片描述

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            vector<int> dp;                 // dp[i] 表示最长递增序列长度为i+1的最小末尾数
            dp.emplace_back(nums[0]);		// dp初始化
            for(int i = 1; i < n; ++i){
                int l = 0;
                int r = dp.size()-1;
                if(dp[r] < nums[i]){        // dp数组最大数小于nums[i],尾部插入元素
                    dp.emplace_back(nums[i]);
                    continue;
                }                           // nums[i]在dp数组范围内
                while(l <= r){              // 二分查找第一个大于等于nums[i]的位置,进行替换
                    int m = l + (r-l) / 2;
                    if(dp[m] >= nums[i]){   // 若非要求严格递增,将此行>=改为>,行10的<改为<=
                        r = m - 1;
                    }else{
                        l = m + 1;
                    }
                }
                dp[l] = nums[i];            // 元素替换
            }
            return dp.size();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    Golang基础-面向过程篇
    spring揭秘总结(一)——spring的Ioc容器
    redis源码分析之IO多路复用
    Cadence 设计快速入门
    基于JAVA球迷信息交流论坛计算机毕业设计源码+数据库+lw文档+系统+部署
    LINUX安装nginx
    拒绝蛮力,高效查看Linux日志文件!
    【车载音视频电脑】嵌入式AI分析车载DVR,支持8路1080P
    参加霍格沃兹测试开发学社举办的火焰杯软件测试开发大赛是一种什么体验?
    C++ explicit关键字使用方法
  • 原文地址:https://blog.csdn.net/qq_19887221/article/details/125408623