📜  最长递增子序列(LIS)的构建和打印LIS序列(1)

📅  最后修改于: 2023-12-03 15:40:17.442000             🧑  作者: Mango

最长递增子序列(LIS)的构建和打印LIS序列

简介

最长递增子序列(Longest Increasing Subsequence,LIS)指的是在一个数字序列中,选取其中的若干个数字组成一个最长的严格递增子序列。

例如,序列1 3 5 4 7 6 8中,最长递增子序列是1 3 5 7 8,长度为5。

本文将介绍构建LIS的动态规划算法,以及打印LIS的方法。

算法思路

设序列a的长度为n,考虑构建一个长度为n的数组dp,其中dp[i]表示以a[i]结尾的最长递增子序列长度。

显然,dp[i]的值是由dp[j]和a[i]决定的,其中j < i且a[j] < a[i],也就是说,如果在a[1]到a[i-1]中存在元素a[j],使得a[j] < a[i],那么dp[i]的值就取dp[j]+1这个最大值。

最终的LIS长度为dp数组中的最大值。

具体实现时,可以使用两个for循环,外层循环遍历a数组,内层循环遍历a数组中i之前的所有元素j,计算dp[i]的值。

代码示例
def LIS(a):
  n = len(a)
  dp = [1] * n # 初始化dp数组为1
  for i in range(n):
    for j in range(i):
      if a[j] < a[i]:
        dp[i] = max(dp[i], dp[j]+1) # 更新dp[i]的值
  return max(dp) # 返回LIS长度

a = [1, 3, 5, 4, 7, 6, 8]
print(LIS(a)) # 输出5

上面的代码实现了LIS的构建,但并没有打印出LIS序列,接下来我们将介绍如何打印LIS序列。

打印LIS序列

在构建LIS的过程中,我们不仅要计算LIS的长度,还要记录LIS的路径,也就是最长递增子序列包含哪些元素。

可以定义一个数组pre,pre[i]表示以a[i]结尾的最长递增子序列中,a[i]前面的那个元素的位置。

在计算dp[i]时,如果存在多个dp[j]+1等于dp[i]的j值,我们选择pre[j]最小的那个j值,以保证得到的LIS是字典序最小的。

最后,可以倒序遍历pre数组,从LIS的最后一个元素开始,一直找到第一个元素,构建出LIS序列。

以下是完整代码:

def LIS(a):
  n = len(a)
  dp = [1] * n # 初始化dp数组为1
  pre = [-1] * n # 初始化pre数组为-1
  for i in range(n):
    for j in range(i):
      if a[j] < a[i] and dp[j] + 1 > dp[i]:
        dp[i] = dp[j] + 1 # 更新dp[i]的值
        pre[i] = j # 记录路径,j是i的前一个元素

  lis_len = max(dp) # LIS长度
  lis = []
  idx = dp.index(lis_len) # 找到LIS的最后一个元素的位置
  while idx != -1:
    lis.append(a[idx])
    idx = pre[idx] # 找到下一个元素的位置
  lis.reverse() # 将LIS反转,得到正常顺序的LIS序列

  return lis_len, lis # 返回LIS长度和LIS序列

a = [1, 3, 5, 4, 7, 6, 8]
lis_len, lis = LIS(a)
print(lis_len) # 输出5
print(lis) # 输出[1, 3, 5, 7, 8]

总结

本文介绍了LIS的构建和打印LIS序列的动态规划算法,并提供了完整的Python代码示例。

LIS是一个经典的动态规划问题,在实际应用中十分常见,例如任务调度、最长公共子序列等领域。掌握动态规划算法和LIS问题解决方法对于程序员而言是非常重要的。