leetcode原題:最長上升子序列(LIS)

題目:給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:

輸入: [10,9,2,5,3,7,101,18]
輸出: 4 
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4

說明:

  • 可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
  • 你算法的時間複雜度應該爲 O(n^2) 。

進階: 你能將算法的時間複雜度降低到 O(n log n) 嗎?

對於此題目,總共有三種思路

 

目錄

一、暴力破解(時間複雜度O(2^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提交運行時間來看,代碼運行時間從秒級降到了毫秒級別,如果規模繼續增加,效果會更加顯著。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章