• [LeetCode] 300. Longest Increasing Subsequence 最长递增子序列


    方法一: 动态规划    时间:O(n*n)  空间 :O(n)

    动态规划的核心设计思想是数学归纳法。

    数学归纳法思想:

      比如我们想证明一个数学结论,那么我们先假设这个结论在 k<n 时成立,然后根据这个假设,

    想办法推导证明出 k=n 的时候此结论也成立。如果能够证明出来,那么就说明这个结论对于 k 等于

    任何数都成立。

    设计动态规划算法:

      找到了问题的「状态」,使用一个dp数组描述、存储问题的状态。假设 dp[0...i-1] 都已经被算

    出来了,然后问自己:怎么通过这些结果算出 dp[i]

    对于本题,求最长递增子序列,

    1. 定义状态:定义dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。

    2.处理 base case:dp[i] 初始值为 1,因为以 nums[i] 结尾的最长递增子序列起码要包含它自己。

    3.分析状态转移:假设 dp[0...i-1] 都已经被算出来了,然后问自己:怎么通过这些结果算出 dp[i]?

    dp[i]  = dp[k]+1; dp[k]满足以下条件:0=<k<i, nums[k]<nums[i], dp[k]是dp[o:i-1]中的最大值。若

    没有满足条件的dp[k],则dp[i] = 1 。具体参考代码中的处理。

    按照以上思路,代码实现如下:

     1 class Solution {
     2 public:
     3     //dp O(n^2)
     4     int lengthOfLIS(vector<int>& nums)
     5     {
     6         if(nums.empty())
     7         {
     8             return 0;
     9         }
    10         const int nums_len = nums.size();  
    11         //base case 
    12         vector<int> dp(nums_len,1);
    13         int res = 1;
    14         for(int i = 1;i<nums_len;++i)
    15         {
    16             for(int j = 0;j<i;++j)
    17             {
    18                 if(nums[i]>nums[j])
    19                 {
    20                     dp[i] = max(dp[i],dp[j]+1);
    21                 }
    22             }
    23             res = max(res,dp[i]);
    24         }
    25         return res;
    26     }
    27    
    28 };

    至此,这道题就解决了,时间复杂度 O(N^2)。总结一下如何找到动态规划的状态转移关系:

        1、明确 dp 数组所存数据的含义。这一步对于任何动态规划问题都很重要,如果不得当

    或者不够清晰,会阻碍之后的步骤。

        2、根据 dp 数组的定义,运用数学归纳法的思想,假设 dp[0...i-1] 都已知,想办法求出 dp[i]

    一旦这一步完成,整个题目基本就解决了。

        但如果无法完成这一步,很可能就是 dp 数组的定义不够恰当,需要重新定义 dp 数组的含义;或者

    可能是 dp 数组存储的信息还不够,不足以推出下一步的答案,需要把 dp 数组扩大成二维数组甚至三维数组。

    方法二.   修改状态定义(同时用到了贪心算法、二分查找)时间O(n * log n)空间 O(n)

     参考:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-er-fen-cha-zhao-tan-xin-suan-fa-p/

    • 定义新状态(特别重要)tail[i]  表示长度为 i + 1 的所有上升子序列的结尾的最小值。

    分析:

      1. 当求数组 nums[0, i] 的最长上升子序列时,已经知道了 nums[0, i-1] 的最长上升子序列的若干个可行解 ,设 nums[0, i-1] 的最长上升

    子序列的长度为 k  (1=< k <= i ),  使用  状态数组  tail [k-1]  记录所有长度 为 k 的 上升子序列的结尾的最小值。那么,

      2. 若 nums[i] > tail[k - 1],  也就是 nums [i]  比 nums[0:i-1] 的尾部元素最小的 最长上升子序列的最后一个元素大,nums[i] 可以接在 其后,

    生成一个尾部为 nums[i] 长度为 k + 1的上升子序列。更新状态数组 tail.push_back (nums[i])

      3. 从第二步可以看出 tail 是个非严格递增数组 ,若nums[i]  < =  tail[k - 1],找到 tail [0 , k - 1 ] 中 第一个 大于等于 nums[i] 的元素 tail [ j ],

                用 nums[i]更新tail [j] ,num[i] 是最新的 长度为 j + 1的递增子序列的最小尾部元素。

     1 class Solution {
     2 public:
     3 //O(n*log n)   O(n)
     4     int lengthOfLIS(vector<int>& nums)
     5     {
     6         vector<int> tail;
     7         tail.push_back(nums[0]);
     8         for(int i = 1;i < nums.size();++i)
     9         {
    10             if(nums[i] > tail.back())
    11             {
    12                 tail.push_back(nums[i]);
    13             }
    14             else
    15             {
    16                 //tail 中第一个大于nums[i]的元素位置,考虑到有相等元素,不能用 upperboud !
    17                  vector<int>::iterator ite = lower_bound(tail.begin(),tail.end(),nums[i]);
    18                  *ite = nums[i];
    19             }
    20         }
    21         return tail.size();
    22     }
    23 };
  • 相关阅读:
    香港中小学校资料
    几本图形学的书
    windows下MBCS和UNICODE编码的转换
    索引测试代码
    Ext.Net一些特别问题(有些未验证)
    MSSQL优化之索引优化(转)
    [转载红鱼儿]kbmmw 开发点滴:kbmmW服务端事务控制
    Devexpress VCL Build v2012 vol 12.2.2 发布
    [转载红鱼儿]kbmmw 开发点滴:kbmMWQuery插入记录
    2013年新年礼物CrossFPC 终于出来了
  • 原文地址:https://www.cnblogs.com/wangxf2019/p/13915227.html
Copyright © 2020-2023  润新知