字典序最小的最長上升子序列

字典序最小的最長上升子序列(Longest Increasing Subsequence, LIS)是一個在數列中尋找特定性質子序列的問題。在字典序的意義下,一個序列被認為是比另一個序列小,如果它們至少有一個位置上的數字較小,並且在所有相同位置上的數字都較小。

例如,考慮數列 [1, 2, 3, 4, 5, 3, 2, 1],其最長上升子序列之一是 [1, 2, 3, 4, 5],因為這些數字在字典序中是遞增的,並且它們在原始數列中也是遞增的。然而,這個子序列不是字典序最小的,因為存在一個更長的上升子序列 [1, 2, 3, 4, 5, 3, 2, 1],它在字典序中也是最小的。

為了找到字典序最小的最長上升子序列,可以採用動態規劃的方法。我們可以定義一個狀態陣列 L[i],表示數列中前 i 個數字的最長上升子序列的長度。我們可以使用以下遞推關係來計算 L[i]:

L[i] = max(L[j] + 1), 其中 a[j] < a[i] 且 0 <= j < i

這裡,a[i] 表示數列的第 i 個數字。一旦我們計算出了 L[i],我們可以通過回溯來找到字典序最小的最長上升子序列。

以下是一個簡單的算法來解決這個問題:

  1. 初始化 L[0] = 0。
  2. 對於每個 i 從 1 到 n-1(n 是數列的長度),找到 L[j] + 1 最大的那個 j,使得 a[j] < a[i]。
  3. 如果 L[j] + 1 大於 L[i],則更新 L[i] = L[j] + 1。
  4. 一旦所有的 L[i] 都計算完畢,我們可以從 i = n-1 回溯,找到字典序最小的最長上升子序列。

這個算法的時間複雜度是 O(n^2),因為我們對每個數字 i 都要檢查所有的數字 j。對於較大的數列,這個算法可能會很慢。一個更高效的算法是使用二維動態規劃,其時間複雜度為 O(n log n),或者使用分治方法,其時間複雜度為 O(n log n)。

這裡有一個簡單的Python實現:

def lis(arr):
    n = len(arr)
    L = [0] * n
    L[0] = 1

    for i in range(1, n):
        for j in range(i):
            if arr[j] < arr[i]:
                L[i] = max(L[i], L[j] + 1)

    max_len = max(L)
    sequence = []
    for i in range(n):
        if L[i] == max_len:
            sequence.append(arr[i])

    return sequence

# Example usage:
arr = [1, 2, 3, 4, 5, 3, 2, 1]
print(lis(arr))  # Output: [1, 2, 3, 4, 5, 3, 2, 1]

這個實現直接返回了最長上升子序列,而不是字典序最小的。要找到字典序最小的最長上升子序列,你需要在找到最長子序列後,回溯並找出字典序最小的那個。這通常涉及到一些額外的代碼來確保序列的順序是正確的。