返回

剑指 Offer 300:最长递增子序列

前端

引子

在计算机科学领域,「最长递增子序列(LIS)」问题是一个经典且重要的算法问题。它要求我们从一个给定的序列中找到一组元素,使得该组元素按其在序列中的出现次序递增,且长度最长。理解 LIS 算法对于解决一系列与子序列相关的实际问题至关重要。

问题剖析

给定一个未经排序的序列 nums,我们希望找出其最长递增子序列。可以将此问题看作是一个动态规划问题,因为它具有以下两个子问题最优解性质:

  1. 最优子结构: 最长递增子序列的子序列也是最长递增子序列。
  2. 重叠子问题: 对于序列中的每个元素,我们需要找到以该元素结尾的最长递增子序列。这可以通过查阅前面所有元素来完成。

动态规划算法

基于子问题最优解性质,我们设计如下动态规划算法:

  1. 初始化一个长度为 nnums 的长度)的数组 dp,其中 dp[i] 表示以序列中第 i 个元素结尾的最长递增子序列的长度。
  2. 对于 i 从 1 到 n,执行以下步骤:
    • 对于 j 从 0 到 i-1,执行以下步骤:
      • 如果 nums[i] > nums[j],则更新 dp[i] = max(dp[i], dp[j] + 1)
  3. 返回 dp 中的最大值。

时间复杂度

该算法的时间复杂度为 O(n²),其中 n 是序列 nums 的长度。对于每个元素,我们需要检查它之前的每个元素,因此总共有 次操作。

空间复杂度

该算法的空间复杂度为 O(n),因为我们需要一个长度为 n 的数组 dp 来存储子问题的结果。

示例

考虑序列 nums = [10, 22, 9, 33, 21, 50, 41, 60, 80]。使用动态规划算法,我们得到以下 dp 数组:

dp = [1, 2, 1, 3, 2, 4, 3, 5, 6]

最长递增子序列的长度为 6,该子序列为 [10, 22, 33, 50, 60, 80]

代码实现(Python)

def length_of_lis(nums):
  """
  返回序列中递增子序列的最长长度。

  参数:
    nums: 未排序的序列。

  返回:
    递增子序列的最大长度。
  """

  n = len(nums)
  dp = [1] * n

  for i in range(1, n):
    for j in range(i):
      if nums[i] > nums[j]:
        dp[i] = max(dp[i], dp[j] + 1)

  return max(dp)

总结

最长递增子序列问题是一个经典的算法问题,可以通过动态规划算法高效解决。该算法的时间复杂度为 O(n²),空间复杂度为 O(n)。理解 LIS 算法对于解决一系列子序列相关问题至关重要,在各种领域有着广阔的应用前景。