返回

动态规划之最长递增子序列

前端

最长递增子序列:理解动态规划与二分查找

什么是最长递增子序列?

想象一下你有一个数字序列,比如 [5, 2, 8, 3, 10, 15, 4, 6, 11]。其中的最长递增子序列是 [2, 3, 4, 6, 10, 11, 15]。它是一个子序列(取自原始序列的元素),其元素依次增大,但不必连续。找到最长递增子序列对于解决各种实际问题很有用,比如股票交易或日程安排。

解决方法:动态规划与二分查找

解决最长递增子序列问题有两种主要方法:动态规划和二分查找。让我们深入探讨每种方法。

动态规划

动态规划是一种分而治之的方法,将问题分解成较小的子问题,然后逐步求解。对于最长递增子序列问题,我们可以定义一个状态转移方程:

dp[i] = max(dp[j] + 1) for all j < i and nums[j] < nums[i]

其中,dp[i] 表示以第 i 个元素结尾的最长递增子序列的长度。我们从 dp[0] = 1 开始,逐步计算 dp[1]、dp[2],依此类推,直到得到 dp[n-1]。

代码示例:

def longest_increasing_subsequence(nums):
  n = len(nums)
  dp = [1] * n

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

  return max(dp)

二分查找

二分查找是一种高效的搜索算法,它利用有序序列的特性。对于最长递增子序列问题,我们使用二分查找来找到当前最长递增子序列的最后一个元素。然后,我们将这个元素添加到当前最长递增子序列中,并重复这个过程,直到找到最长递增子序列。

代码示例:

def longest_increasing_subsequence(nums):
  n = len(nums)
  dp = [nums[0]]

  for i in range(1, n):
    if nums[i] > dp[-1]:
      dp.append(nums[i])
    else:
      index = bisect.bisect_left(dp, nums[i])
      dp[index] = nums[i]

  return len(dp)

比较

动态规划:

  • 优点:简单易懂,实现容易。
  • 缺点:时间复杂度为 O(n^2)。

二分查找:

  • 优点:时间复杂度为 O(nlogn),速度快。
  • 缺点:需要对序列进行排序。

选择哪种方法取决于具体问题。对于较短的序列或不需要知道最长递增子序列的具体元素,动态规划是一个不错的选择。对于较长的序列或需要知道最长递增子序列的具体元素,二分查找是一个更好的选择。

常见问题解答

1. 最长递增子序列的长度最大是多少?

答案:它等于序列中的唯一元素数量。

2. 如果序列中有多个元素相同,最长递增子序列的长度是多少?

答案:它等于序列中唯一元素的数量,包括重复元素。

3. 如何找到最长递增子序列的具体元素?

答案:对于动态规划,我们可以使用一个辅助数组来跟踪最长递增子序列中每个元素的来源。对于二分查找,我们可以使用一个堆栈来存储最长递增子序列的元素。

4. 最长递增子序列有什么实际应用?

答案:它用于各种应用中,包括:

  • 股票交易:识别股价的上升趋势。
  • 日程安排:安排任务以最大化效率。
  • 数据分析:查找时间序列中的模式。

5. 除了动态规划和二分查找,还有其他解决最长递增子序列问题的方法吗?

答案:有,比如贪心算法和后缀数组。