链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n^2) 。
进阶: 你能将算法的时间复杂度降低到 O(n*logn) 吗?
1、定义数组元素的含义
首先需要对数组做一个通用的定义,当我们考虑前 i 个元素时,定义 dp[i] 为以第 i 个元素为结尾时的最长上升子序列的长度,注意我们选取 nums[i] 在其中,所有 dp[i]的长度时包含 nums[i] 在里面的。
2、寻找递推表达式
我们需要计算出 dp[i] 的递推公式,而在计算 dp[i] 之前,我们其实已经计算出 dp[0],dp[1]...dp[i-1] 的值,所以我们可以根据 dp[i] 之前的计算结果找到 dp[i]。
dp[i]=max(dp[i],dp[j]+1),其中 0≤j<i 且 num[j]<num[i]
3、找到初始值和边界值
我们可以定义初始值为 1,因为 1 个元素的上升子序列长度为 1,所以 dp[i]=1。
时间复杂度:O(n^2)
空间复杂度:O(n),使用了长度为 n 的 dp 数组。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp=len(nums)*[1]
for i in range(len(nums)):
for j in range(i):
if nums[i]>nums[j]:
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
从动态规划的解法我们可以看到,时间复杂度为 O(n^2),而进阶版本要求时间复杂度为 O(nlogn),而如何能达到这个时间复杂度呢?
让我们想一想,题目中有数组,我们是不是可以用二分查找呢,而且二分查找的时间复杂度是 O(logn),是不是好开心。但是问题来了,二分查找只能用在有序的数组上面呀,而题目给出的数组根本不是有序的。
虽然题目中给出的数组不是有序的,但是答案中要求的上升子序列是有序的呀,所以我们可以从这个上升子序列下手。
定义 dp 数组,表示所有长度为 i 的上升子序列中, 末尾元素最小值;
定义 count 为最长上升子序列的长度;
在循环中,设置 left=0,right=count,在这里开启二分查找 mid=(left+right)//2;
如果 dp[mid]>=nums[i],代表 dp数组中 mid 到末尾的数字均大于 nums[i],这时使得 right=mid,继续进行二分判断;
如果 dp[mid]<nums[i],代表 dp数组中 0 到 mid 且包含 mid 的数字均小于 nums[i],这时使得 left=mid+1,继续进行二分判断;
如果一开始 left=right=0,不执行 while 循环,那说明应该向 dp 中直接插入第一个元素,dp[left]=nums[i],同时 count 的长度加 1;
如果执行了 while 循环中的二分查找,因为我们要找的是最长上升子序列,所以 dp[left]=nums[i],同时也要判断 left是否等于 count。
写了两种二分查找,大家可以自行对比下区别,二分查找的细节问题太多了,大家一定要注意。
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
n=len(nums)
dp=n*[0]
count=0
for i in range(n):
left,right=0,count
while left<right:
mid=left+((right-left)>>1)
if dp[mid]>=nums[i]:
right=mid
else:
left=mid+1
if left==count:
count+=1
dp[left]=nums[i]
return count
class Solution(object):
def lengthOfLIS(self, nums):
if not nums:
return 0
dp=[]
for num in nums:
if not dp or num>dp[-1]:
dp.append(num)
else:
left,right=0,len(dp)-1
loc=right
while left <= right:
mid=left+((right-left)>>1)
if dp[mid] >= num:
loc=mid
right=mid-1
else:
left=mid+1
dp[loc]=num
return len(dp)
如果觉得文章不错,希望大家可以关注我噢,点赞、收藏、在看、分享就再好不过了。如果有任何建议和问题,可以在下方给我留言,我会不定期更新更多的文章,祝我们终将自由。