300. 最长递增子序列
题目描述
给定一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
- 1 <= nums.length <= 2500
- -10^4 <= nums[i] <= 10^4
题目解析
解法1 - 动态规划
这道题目是一道经典的动态规划问题,我们可以用动态规划来解决。
设 dp[i]
表示以 nums[i]
结尾的最长递增子序列的长度。
那么,dp[i]
的值可以由 dp[j]
推导出来,其中 0 <= j < i
。
如果 nums[j] < nums[i]
,那么 dp[i]
就可以由 dp[j]
推导出来,因为 nums[j]
一定是 nums[i]
的前驱,所以 dp[i]
一定大于等于 dp[j]
加 1。
状态转移方程:
if nums[j] < nums[i]
dp[i] = max(dp[j] + 1) ,0 <= j < i
最后,我们可以返回 dp
数组中的最大值,即为最长递增子序列的长度。
复杂度分析:
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是数组的长度。
- 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。
解法2 - 二分查找
我们可以用二分查找来优化解法1。
我们可以维护一个数组 tails
,表示当前最长的递增子序列。
遍历数组 nums
,对于每个 nums[i]
,如果nums[i]
大于tails
中的最大值,我们可以将nums[i]
加入到tails
中。
否则,我们可以用二分查找来找到 tails
中第一个大于等于 nums[i]
的元素 tail
,并使用nums[i]
来替换掉tail。
最后,tails
中的元素个数即为最长递增子序列的长度。
其二分法的核心思想是:插入数据替换比他大的最小的那个,保证这个队列始终是在保证最长的情况下,值最小的那个。
复杂度分析:
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),其中 n n n 是数组的长度。
- 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。
代码实现
解法1 - 动态规划
Go版本:
func lengthOfLIS(nums []int) int {
n:=len(nums)
dp:=make([]int,n)
res:=1
dp[0]=1
for i:=1;i<n;i++{
dp[i]=1
for j:=0;j<i;j++{
if(nums[i]>nums[j]){
dp[i]=max(dp[i],dp[j]+1)
}
}
res=max(res,dp[i])
}
return res
}
C++版本:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==1){
return 1;
}
vector<int> dp(n,0);
int res=0;
dp[0]=1;
for(int i=1;i<n;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
dp[i]=max(dp[i],dp[j]+1);
}
}
res=max(res,dp[i]);
}
return res;
}
};
解法2 - 二分查找
Python版本:
class Solution(object):
def lengthOfLIS(self, nums):
n = len(nums)
if n==1:
return 1
tails = [nums[0]]
for i in range(n):
if nums[i]>tails[-1]:
tails.append(nums[i])
continue
l,r = 0,len(tails)-1
while l<r:
mid = l + (r - l) // 2
if tails[mid]<nums[i]:
l = mid + 1
else:
r = mid
tails[l] = nums[i]
return len(tails)