数组元素之和最小化 | 豆包MarsCode AI刷题

51 阅读4分钟

最长递增子序列问题笔记

问题背景与建模

最长递增子序列(LIS)  是经典的动态规划问题之一。其核心在于在一个给定的序列中,找到一个最长的子序列,使得这个子序列中的元素是严格递增的。该问题不仅在学术研究中具有重要地位,还在许多实际应用中发挥作用,例如基因序列分析、股票价格预测等。

问题定义:

给定一个长度为 n 的序列 nums,我们需要找到一个最长的子序列,使得这个子序列中的元素是严格递增的。

输入输出:

  • 输入:一个数组 nums,表示给定的序列。
  • 输出:最长递增子序列的长度。

分析与思考

解决该问题需要综合考虑以下两个因素:

  1. 递增性:子序列中的元素必须是严格递增的。
  2. 最长性:我们需要找到最长的满足递增条件的子序列。

由于无法直接穷举所有可能的子序列(组合数为 2^n,当 n 较大时不可行),我们需要更高效的算法。动态规划是解决此类问题的理想工具。

动态规划思路

3.1 状态定义

设 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。这里,i 的范围是从 0 到 n-1

  • dp[0] 表示以 nums[0] 结尾的最长递增子序列的长度(始终为 1)。
  • dp[n-1] 表示以 nums[n-1] 结尾的最长递增子序列的长度,即我们要求解的目标之一。
3.2 转移方程

对于每个元素 nums[i],我们需要考虑它之前的所有元素 nums[j](其中 j < i):

  • 如果 nums[i] > nums[j],则 nums[i] 可以接在 nums[j] 后面形成一个递增子序列。
  • 因此,状态转移方程为:

[
dp[i] = \max(dp[i], dp[j] + 1) \quad \text{for all } j < i \text{ and } nums[j] < nums[i]
]

3.3 初始化与边界条件
  • 初始时,每个元素自身可以构成一个长度为 1 的递增子序列,即 dp[i] = 1 对于所有 i
3.4 遍历顺序
  • 外层遍历 i,表示当前考虑的元素。
  • 内层遍历 j,表示在 i 之前的元素,检查是否可以形成递增子序列。
4.1 复杂度分析
  • 时间复杂度:动态规划的核心是双重循环。外层遍历 n 个元素,内层遍历 i 之前的元素,因此时间复杂度为 (O(n^2))。
  • 空间复杂度:我们只使用一个一维数组 dp,因此空间复杂度为 (O(n))。

解题步骤:

  1. 初始化 dp 数组dp = [1, 1, 1, 1, 1, 1, 1, 1]

  2. 考虑第 1 个元素 nums[1] = 9

    • 9 不能接在 10 后面,dp[1] 保持为 1
  3. 考虑第 2 个元素 nums[2] = 2

    • 2 不能接在 10 和 9 后面,dp[2] 保持为 1
  4. 考虑第 3 个元素 nums[3] = 5

    • 5 可以接在 2 后面,dp[3] = max(dp[3], dp[2] + 1) = 2
  5. 考虑第 4 个元素 nums[4] = 3

    • 3 可以接在 2 后面,dp[4] = max(dp[4], dp[2] + 1) = 2
  6. 考虑第 5 个元素 nums[5] = 7

    • 7 可以接在 5 和 3 后面,dp[5] = max(dp[5], dp[3] + 1, dp[4] + 1) = 3
  7. 考虑第 6 个元素 nums[6] = 101

    • 101 可以接在 7 后面,dp[6] = max(dp[6], dp[5] + 1) = 4
  8. 考虑第 7 个元素 nums[7] = 18

    • 18 可以接在 7 后面,dp[7] = max(dp[7], dp[5] + 1) = 4

最终结果dp = [1, 1, 1, 2, 2, 3, 4, 4],最长递增子序列的长度为 4

总结与启发

最长递增子序列问题虽然是一道经典的算法题,但它的解决思路远不局限于“写出正确代码”。在解决问题的过程中,我们需要:

  1. 从问题建模开始,明确输入、输出和约束条件。
  2. 分析解法的核心思路,找出限制因素与优化目标。
  3. 权衡解法优劣,结合实际需求选择合适的算法。

更重要的是,最长递增子序列的思想广泛应用于现实场景,例如基因序列分析、股票价格预测等。在这些应用中,动态规划的思维和解决方法,能够帮助我们找到更高效、更科学的解决方案。