[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 };
原文地址:https://www.cnblogs.com/wangxf2019/p/13915227.html