给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
老动态规划了
设 dp[i] 是以 nums[i] 为结尾的最长严格递增子序列的长度,则:
dp[i] = max(dp[j]+1), 0 <= j < i
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
dp = []
for i in range(len(nums)):
dp.append(1)
for j in range(i):
if nums[i]>nums[j]:
dp[i] = max(dp[j]+1, dp[i])
return max(dp)
官方题解中还使用了贪心+二分查找法,时间复杂度更低:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
d = []
for n in nums:
if not d or n > d[-1]:
d.append(n)
else:
l, r = 0, len(d) - 1
loc = r
while l <= r:
mid = (l + r) // 2
if d[mid] >= n:
loc = mid
r = mid - 1
else:
l = mid + 1
d[loc] = n
return len(d)
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
基于上面的贪心思路,我们维护一个数组 d[i]d[i]d[i] ,表示长度为 iii 的最长上升子序列的末尾元素的最小值,用 len\textit{len}len 记录目前最长上升子序列的长度,起始时 lenlenlen 为 111,d[1]=nums[0]d[1] = \textit{nums}[0]d[1]=nums[0]。
同时我们可以注意到 d[i]d[i]d[i] 是关于 iii 单调递增的。因为如果 d[j]≥d[i]d[j] \geq d[i]d[j]≥d[i] 且 j<ij < ij<i,我们考虑从长度为 iii 的最长上升子序列的末尾删除 i−ji-ji−j 个元素,那么这个序列长度变为 jjj ,且第 jjj 个元素 xxx(末尾元素)必然小于 d[i]d[i]d[i],也就小于 d[j]d[j]d[j]。那么我们就找到了一个长度为 jjj 的最长上升子序列,并且末尾元素比 d[j]d[j]d[j] 小,从而产生了矛盾。因此数组 ddd 的单调性得证。
我们依次遍历数组 nums\textit{nums}nums 中的每个元素,并更新数组 ddd 和 lenlenlen 的值。如果 nums[i]>d[len]\textit{nums}[i] > d[\textit{len}]nums[i]>d[len] 则更新 len=len+1len = len + 1len=len+1,否则在 d[1…len]d[1 \ldots len]d[1…len]中找满足 d[i−1]<nums[j]<d[i]d[i - 1] < \textit{nums}[j] < d[i]d[i−1]<nums[j]<d[i] 的下标 iii,并更新 d[i]=nums[j]d[i] = \textit{nums}[j]d[i]=nums[j]。
根据 ddd 数组的单调性,我们可以使用二分查找寻找下标 iii,优化时间复杂度。
最后整个算法流程为:
-
设当前已求出的最长上升子序列的长度为 len\textit{len}len(初始时为 111),从前往后遍历数组 nums\textit{nums}nums,在遍历到 nums[i]\textit{nums}[i]nums[i] 时:
-
如果 nums[i]>d[len]\textit{nums}[i] > d[\textit{len}]nums[i]>d[len] ,则直接加入到 ddd 数组末尾,并更新 len=len+1\textit{len} = \textit{len} + 1len=len+1;
-
否则,在 ddd 数组中二分查找,找到第一个比 nums[i]\textit{nums}[i]nums[i] 小的数 d[k]d[k]d[k] ,并更新 d[k+1]=nums[i]d[k + 1] = \textit{nums}[i]d[k+1]=nums[i]。
以输入序列 [0,8,4,12,2][0, 8, 4, 12, 2][0,8,4,12,2] 为例:
第一步插入 000,d=[0]d = [0]d=[0];
第二步插入 888,d=[0,8]d = [0, 8]d=[0,8];
第三步插入 444,d=[0,4]d = [0, 4]d=[0,4];
第四步插入 121212,d=[0,4,12]d = [0, 4, 12]d=[0,4,12];
第五步插入 222,d=[0,2,12]d = [0, 2, 12]d=[0,2,12]。
最终得到最大递增子序列长度为 333。