返回

解决 LeetCode 646. 最长数对链——掌握序列 DP 技巧

后端

LeetCode 646:最长数对链——掌握序列 DP 算法的进阶指南

简介

踏入 LeetCode 646:最长数对链的精彩世界,我们将开启一场算法之旅,深入探索序列动态规划 (DP) 的奥秘。作为一道广受青睐的面试考题,LeetCode 646 不仅考验你的算法功底,更能让你对序列 DP 的本质有深入理解。准备好在这场算法冒险中大展身手了吗?

问题陈述

设有 n 个数对,每个数对由两个正整数 a 和 b 组成。你的任务是找到最长的数对链,其中链中的每个数对 (a1, b1) 和 (a2, b2) 满足条件 a1 < a2 且 b1 < b2。

思路与解析

贪心算法:步步为营

LeetCode 646 的解题思路可归结为贪心算法的范畴。贪心算法在每一步都做出看似局部最优的选择,期望最终达到全局最优结果。具体而言,我们可以按如下步骤构建最长数对链:

  1. 初始化一个长度为 1 的数对链,链中初始元素为题目给定的第一个数对。
  2. 将剩余数对按照第一个数从小到大排序。
  3. 从排序后的数对中选取第一个数对,将其添加到数对链末尾。
  4. 重复步骤 3,直至所有数对都被加入数对链。

序列 DP:巧解全局

为优化贪心算法的时间复杂度,我们引入序列 DP 的思想。序列 DP 将问题分解成一系列子问题,通过逐一解决子问题进而求解最终问题。在 LeetCode 646 中,我们可以将问题分解为若干个子问题,即对于每个数对,求出以该数对为结尾的最长数对链长度。我们可以借助长度为 n 的数组 dp 来存储子问题解,其中 dp[i] 表示以第 i 个数对为结尾的最长数对链长度。

  1. 初始化 dp 数组,使 dp[i] = 1,表示对于每个数对,以其为结尾的最长数对链长度为 1。
  2. 从第二个数对开始,依次计算每个数对的子问题解。
  3. 对于第 i 个数对,若存在某个数对 j 满足 j < i 且 dp[j] + 1 > dp[i],则更新 dp[i] = dp[j] + 1。
  4. 重复步骤 3,直至处理完所有数对。

通过上述步骤,我们最终得到了 dp 数组,其中最大值即为最终问题解。

代码实现:Python

def max_chain_length(pairs):
    """
    返回最长数对链的长度。

    参数:
        pairs:列表,包含 n 个数对,每个数对由两个正整数 a 和 b 组成。

    返回:
        最长数对链的长度。
    """

    # 初始化 dp 数组
    dp = [1] * len(pairs)

    # 计算每个数对的子问题解
    for i in range(1, len(pairs)):
        for j in range(i):
            if pairs[j][1] < pairs[i][0]:
                dp[i] = max(dp[i], dp[j] + 1)

    # 返回 dp 数组中的最大值
    return max(dp)


# 测试用例
pairs = [[1, 2], [2, 3], [3, 4], [1, 5]]
print(max_chain_length(pairs))  # 输出:3

结语

通过对 LeetCode 646:最长数对链的深入剖析,相信你已经牢牢掌握了序列 DP 算法的精髓。愿这篇文章成为你 LeetCode 征程中的一盏明灯,照亮你攻克算法难题的道路。若对序列 DP 或其他算法问题有任何困惑,欢迎随时交流探讨。

常见问题解答

1. 为什么贪心算法不能保证得到最长数对链?

贪心算法在每步选择局部最优解,但不能保证全局最优。在某些情况下,贪心算法可能无法找到最长数对链。

2. 序列 DP 如何优化贪心算法?

序列 DP 通过存储子问题解来避免重复计算,从而优化了贪心算法的时间复杂度。

3. 如何处理数对出现重复的情况?

在处理数对重复时,我们可以对数对进行哈希或使用并查集来记录数对之间的关系,以确保不会重复计算。

4. 除了序列 DP,还有哪些其他算法可以解决 LeetCode 646?

除了序列 DP,还可以使用二分查找或树形结构来解决 LeetCode 646,但这些算法的时间复杂度可能更高。

5. 在实际场景中,序列 DP 有哪些应用?

序列 DP 在许多实际场景中都有应用,例如最长公共子序列、背包问题和最长上升子序列问题。