方法一: 动态规划 时间: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 };