Nlogn求最長上升子序列
最長上升子序列(Longest Increasing Subsequence,LIS)問題是一個著名的動態規劃問題。對於一個給定的序列,最長上升子序列問題要求我們找到其中最長的一個子序列,並且這個子序列中的元素是遞增的。
例如,給定序列 [10, 22, 9, 33, 21, 50]
,它的最長上升子序列是 [9, 21, 33]
,長度為3。
對於一個序列 a[0], a[1], ..., a[n-1]
,我們可以通過以下動態規劃算法來找到它的最長上升子序列:
- 初始化一個
n
維的陣列dp
,其中dp[i]
表示a[0], a[1], ..., a[i-1]
的最長上升子序列的長度。 - 初始化
dp[0] = 1
,因為任何單個元素都是它自己的最長上升子序列。 - 對於
i = 1 to n-1
,我們找到dp[i]
作為dp[0] to dp[i-1]
的最大值加上1
,如果a[i]
比a[dp[0]...dp[i-1]]
中的最大元素還要大的話。
以下是一個Python的實現:
def lis(a):
n = len(a)
dp = [1] * n # 初始化最長上升子序列長度為1
for i in range(1, n):
for j in range(i):
if a[i] > a[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
# 示例
a = [10, 22, 9, 33, 21, 50]
print(lis(a)) # 輸出: 3
這個算法的時間複雜度是 O(n^2)
,因為我們遍歷了整個序列兩次。對於較大的 n
,這個時間複雜度可能會很高。
對於 nlogn
的時間複雜度,我們可以使用二分搜尋來加速查找過程。這個方法通常稱為二分搜尋 + 動態規劃法。這個算法的基本思路是:
- 對於每個
i
,我們使用二分搜尋在a[0], a[1], ..., a[i-1]
中找到a[i]
的插入位置pos
。 - 然後我們更新
dp[i] = dp[pos] + 1
。
這樣,我們就可以將時間複雜度降低到 O(nlogn)
,但是空間複雜度仍然是 O(n)
。
以下是一個使用二分搜尋的Python實現:
def binary_search(a, i):
lo, hi = 0, i
while lo <= hi:
mid = (lo + hi) // 2
if a[mid] > a[i]:
hi = mid - 1
elif a[mid] < a[i]:
lo = mid + 1
else:
return mid
return -1
def lis(a):
n = len(a)
dp = [1] * n
for i in range(n):
pos = binary_search(a, i)
if pos == -1:
dp[i] = 1
else:
dp[i] = dp[pos] + 1
return max(dp)
# 示例
a = [10, 22, 9, 33, 21, 50]
print(lis(a)) # 輸出: 3
這個算法的時間複雜度是 O(nlogn)
,因為我們對每個元素 a[i]
進行了 O(logn)
的二分搜尋。空間複雜度仍然是 O(n)
,因為我們使用了 dp
陣列來存儲最長上升子序列的長度。