题目链接:https://leetcode.com/problems/longest-increasing-subsequence/
题目内容:
Given an unsorted array of integers, find the length of longest increasing subsequence.
For example,
Given [10, 9, 2, 5, 3, 7, 101, 18]
,
The longest increasing subsequence is [2, 3, 7, 101]
, therefore the length is 4
. Note that there may be more than one LIS combination, it is only necessary for you to return the length.
Your algorithm should run in O(n^2)
complexity.
Follow up: Could you improve it to O(nlogn)
time complexity?
题目解法
这是一道典型的动态规划问题,通常有两种解法,一种自然的思想时间复杂度为O(n^2)
,而另一种巧妙地思路可以利用二分查找把时间复杂度降低到O(nlogn)
。下面分别介绍这两种做法。
首先我们约定nums为输入容器,下标从0开始。
解法一
设
dp[i]
表示以nums[i]
结尾的最长递增子列(LIS
)的长度。由于最短的LIS就是自己本身,因此我们先初始化dp[.]=1
。接着,从前到后遍历整个nums
容器,按照下面的规则更新dp。dp[i] = max{dp[j]+1|0<=j<i,nums[i] > nums[j]}
通俗来说,也就是第i个位置为结尾的LIS长度,取决于前面0~i-1位置的LIS长度,如果前面的LIS的最后一个元素比当前位置的元素小(
nums[i] > nums[j]
),则可以把当前的结尾加入到前面的LIS尾部,构成一个比原来长度+1的LIS。因为我们是从前到后来更新dp,因此在更新dp[i]时已经得到了所有的dp[j],j<i
,我们只需要从中选取最大的。在遍历结束后,也就得到了以每个数为结尾的LIS,只要把dp排序,取出最大的元素即为整个序列的LIS。
代码如下:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int cnt = nums.size(); int *dp = (int*)malloc(cnt*sizeof(int)); for(int i = 0; i < cnt; i++) dp[i] = 1; for(int i = 1; i < cnt; i++){ for(int j = 0; j < i; j++){ if(nums[j] < nums[i] && dp[i] < dp[j] + 1){ dp[i] = dp[j] + 1; } } } sort(dp,dp+cnt); return dp[cnt - 1]; } };
解法二
设
d[len]
表示长度为len的最长子列的最小末尾元素,这里之所以说是最小,是因为可能出现多个解,而显然其中结尾最小的最具有变得更长的潜力,因此我们应该选取结尾最小的作为最优解。当我们遍历nums[i]
中的每个元素时,都能在d[len]
中找到合适的位置插入它,规则为:当
nums[i]
比d[len]
还大,说明可以插入到当前LIS的尾部,也就是可以找到一个更长的LIS,这时候我们更新d[++len]=nums[i]
,这样不仅更新了LIS的尾部,也更新了长度。当
nums[i]
不比d[len]
大时,并不能改变LIS的长度,但是可以对LIS进行优化,比如更新一个长度比len
小的LIS的尾部元素为更小的值,虽然一次这样的更新可能无法带来len
的增加,但是可以减小d[len]
,例如序列10,9,2,5,3,7
,我们开始会得到d[1]=10
,接着9
的到来会更新d[1]=9
,2
的到来会更新d[1]=2
,只有这样,当5
到来时,才能得到5>d[1]
,从而得到d[2]=5
。
按照上面的描述,我们发现算法的关键不再是对
len
的计算,而是元素插入位置的计算,插入元素的时间复杂度即为找到LIS的时间复杂度,因此我们使用二分查找
来插入,由于我们所谓的插入其实是一种覆盖
,因此应该找到下界来防止d[len]
被错误地覆盖,使用STL的lower_bound
即可。代码如下:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int cnt = nums.size(); if(cnt == 0) return 0; int *d = (int*)malloc((cnt+1)*sizeof(int)); int len = 1; d[len] = nums[0]; for(int i = 1; i < cnt; i++){ if(nums[i]>d[len]){ d[++len]=nums[i]; }else{ int pos = lower_bound(d,d+len,nums[i]) - d; d[pos] = nums[i]; } } return len; } };