题目:给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入:[10,9,2,5,3,7,101,18]
输出: 4 解释: 最长的上升子序列是[2,3,7,101],
它的长度是 4
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n^2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
对于此题目,总共有三种思路
目录
二、采用动态规划(时间复杂度O(n^2),空间复杂度O(n))
三、使用二分查找法(时间复杂度O(nlogn),空间复杂度O(n))
一、暴力破解(时间复杂度O(2^n))
我们可以分别给定每个数字是否选取,比如给定list=[2,3],那么存在可能以下几种情况
len([])=0
len([2])=len([3])=1
len([2,3])=2
每个数字有存在与否两种状态,可以采用深度搜索的方法搜索所有可能存在的字串,最后计算其中最大的长度即可
由于此方法理解思路即可,因其过于夸张的时间代价,基本不存在应用可能
二、采用动态规划(时间复杂度O(n^2),空间复杂度O(n))
leetcode 提交运行时间:1240ms
采用动态规划可解此问题
基本思想:定义一个同样大小的list,与给定数组相同的索引出存储一个值,该值表示原list取该索引的值之时的最大长度
代码如下
#Python3
def DP_LIS(nums):
size=len(nums)
if size<=1:
return size
dp=[1]*size
for i in range(1,size):
for j in range(i):
if nums[i]>nums[j]:
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
if __name__ == '__main__':
test=[10,9,2,5,3,7,101,18,20]
print(DP_LIS(test))
可以看到,每个状态的值取决于之前所有状态的最大值和当前值的大小。
运行结果:
5
[Finished in 0.2s]
很明显,最长上升子序列为[2,5,7,18,20]或者[2,3,7,18,10],长度为5
三、使用二分查找法(时间复杂度O(nlogn),空间复杂度O(n))
leetcode提交运行时间:80ms
基本思想:只要子串中某个元素可以更小,便使用更小的元素进行替换。如果从后面的子串收益的长度超过了原来的长度,字串自然会更新,如果从后面的子串收益得到的长度低于本来的长度,得到的字串长度不会改变,对其使用len方法自然会得到最长上升子序列的长度
此方法和动态规划类似,不同的是它利用了子序列的“上升”特性:即上升的子序列是有序的,可以利用有序这个特性,采用二分查找方法找到将新的元素置放在此序列中的位置所在,若新元素比子序列中所有元素都要大,则将新元素添加在子序列之后,子序列长度加1,否则则使用该元素替换掉字串中比新元素大的最小的数,子序列长度不变即可。
代码如下:
# Python3
# O(nlogn) 利用二分搜索法
def binLIS(nums):
size=len(nums)
if size<=1:
return size
tmp=[nums[0]]
for i in range(1,size):
if nums[i]>tmp[-1]:
tmp.append(nums[i])
else:
index=findIndex(tmp,nums[i])
tmp[index]=nums[i]
return len(tmp)
def findIndex(nums,n):
# 已经确定nums[-1]>=n
start,end=0,len(nums)-1
if end==0 or nums[0]>=n:
return 0
if nums[-1]==n:
return -1
while start<=end:
mid=int((start+end)/2)
if nums[mid]<=n and nums[mid+1]>=n:
return mid if nums[mid]==n else mid+1
if nums[mid]>n:
end=mid
else:
start=mid
if __name__ == '__main__':
test=[10,9,2,5,3,4]
print(binLIS(test))
可以看到,它缩减时间复杂度的原理在于使用二分法替换了动态规划中的第二层循环,二分法的时间复杂度为O(logn),再结合外层循环,使得时间复杂度从O(n^2)降到了O(nlogn)
运行结果:
3
[Finished in 0.2s]
由于测试数据规模较小,并且在sublime中运行的时间只能精确到0.1s,无法体现其优势所在,但从leetcode提交运行时间来看,代码运行时间从秒级降到了毫秒级别,如果规模继续增加,效果会更加显著。