Nlogn求最長上升子序列

最長上升子序列(Longest Increasing Subsequence,LIS)問題是一個著名的動態規劃問題。對於一個給定的序列,最長上升子序列問題要求我們找到其中最長的一個子序列,並且這個子序列中的元素是遞增的。

例如,給定序列 [10, 22, 9, 33, 21, 50],它的最長上升子序列是 [9, 21, 33],長度為3。

對於一個序列 a[0], a[1], ..., a[n-1],我們可以通過以下動態規劃算法來找到它的最長上升子序列:

  1. 初始化一個 n 維的陣列 dp,其中 dp[i] 表示 a[0], a[1], ..., a[i-1] 的最長上升子序列的長度。
  2. 初始化 dp[0] = 1,因為任何單個元素都是它自己的最長上升子序列。
  3. 對於 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 的時間複雜度,我們可以使用二分搜尋來加速查找過程。這個方法通常稱為二分搜尋 + 動態規劃法。這個算法的基本思路是:

  1. 對於每個 i,我們使用二分搜尋在 a[0], a[1], ..., a[i-1] 中找到 a[i] 的插入位置 pos
  2. 然後我們更新 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 陣列來存儲最長上升子序列的長度。