刷题学习心得:动态规划问题解析

45 阅读6分钟

刷题学习心得:动态规划问题解析

在使用豆包和 MarsCode AI 刷题的过程中,我遇到了许多富有挑战性的题目,其中动态规划问题给我留下了深刻的印象。通过对这些问题的钻研,我收获了宝贵的知识和经验。

题目解析:最长公共子序列问题

思路

最长公共子序列(Longest Common Subsequence,LCS)问题可以使用动态规划来解决。思路是创建一个二维数组dp[i][j],其中i表示第一个序列的索引,j表示第二个序列的索引。dp[i][j]的值表示第一个序列的前i个元素和第二个序列的前j个元素的最长公共子序列的长度。

初始化时,当i = 0j = 0时,dp[i][j] = 0,因为空序列没有公共子序列。然后,对于i > 0j > 0的情况,如果两个序列的第i个元素和第j个元素相等,那么dp[i][j] = dp[i - 1][j - 1] + 1,这意味着最长公共子序列长度增加 1。如果不相等,则dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]),取之前计算的两个子问题中的最大值。

图解

假设我们有两个序列sequence1 = [1, 3, 4, 5, 6, 7]sequence2 = [2, 3, 5, 7]

  • 构建dp数组,dp[0][0] = 0,因为两个空序列没有公共子序列。当i = 0j从 0 增加时,dp[0][j] = 0;同理,当j = 0i从 0 增加时,dp[i][0] = 0
  • i = 1j = 1时,sequence1[0] = 1sequence2[0] = 2,两者不相等,所以dp[1][1] = Math.max(dp[0][1], dp[1][0]) = 0
  • i = 2j = 2时,sequence1[1] = 3sequence2[1] = 3,两者相等,所以dp[2][2] = dp[1][1] + 1 = 1
  • 以此类推,填充整个dp数组,最后dp[sequence1.length][sequence2.length]的值就是最长公共子序列的长度。

代码详解(Java)

java

public class LongestCommonSubsequence {
    public static int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m + 1][n + 1];

        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                if (i == 0 || j == 0) {
                    dp[i][j] = 0;
                } else if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        return dp[m][n];
    }

    public static void main(String[] args) {
        String sequence1 = "134567";
        String sequence2 = "2357";
        int result = longestCommonSubsequence(sequence1, sequence2);
        System.out.println("最长公共子序列长度: " + result);
    }
}

在这段代码中:

  • 首先创建了dp二维数组,大小为(m + 1)×(n + 1),用于存储中间结果。
  • 外层for循环遍历text1的长度(包括空序列情况,索引从 0 到m),内层for循环遍历text2的长度(索引从 0 到n)。
  • i = 0j = 0时,初始化dp[i][j] = 0。当text1text2当前位置的字符相等时,更新dp[i][j]dp[i - 1][j - 1] + 1;否则,取dp[i - 1][j]dp[i][j - 1]中的最大值。
  • 最后,dp[m][n]就是最长公共子序列的长度,在main方法中进行了简单的测试。

知识总结

动态规划的核心原理

动态规划的核心是通过存储子问题的解来避免重复计算,从而提高算法效率。在最长公共子序列问题中,dp数组存储了不同长度的子序列的最长公共子序列长度,这使得我们可以在计算dp[i][j]时快速获取已经计算过的子问题的解。这种思想在解决许多具有重叠子问题和最优子结构性质的问题时非常有效。

状态转移方程的理解与构建

状态转移方程是动态规划的关键。在 LCS 问题中,dp[i][j]的状态转移方程根据text1[i - 1]text2[j - 1]是否相等有不同的计算方式。理解和构建状态转移方程需要对问题的结构有深入的分析,找到子问题之间的关系和递推规律。正确的状态转移方程能够准确地描述问题从一个状态到另一个状态的转换过程。

动态规划与其他算法的结合

动态规划可以与其他算法或数据结构结合使用。例如,在一些复杂的动态规划问题中,可能需要使用哈希表来优化查找子问题的解,或者结合贪心算法来进一步提高效率。这种多算法、多数据结构的结合方式拓宽了我们解决问题的思路,使我们能够应对更复杂的编程挑战。

学习建议

深入理解问题本质

对于动态规划问题,首先要深入理解问题的本质和要求。尝试通过手动计算一些简单的例子来找到问题的规律,比如在 LCS 问题中,可以手动列出两个短序列的所有可能子序列,观察最长公共子序列是如何形成的。这种手动模拟的过程有助于理解问题的结构和动态规划的应用场景。

多练习不同类型的动态规划题目

动态规划有多种类型的题目,如背包问题、最长递增子序列问题等。每种类型的题目都有其独特的特点和解题思路。通过大量练习不同类型的题目,可以熟悉动态规划的各种应用场景,掌握不同的状态转移方程构建方法和优化技巧。同时,在练习过程中要注意总结相似问题之间的共性和差异,形成自己的解题模板。

分析时间和空间复杂度

在学习动态规划时,要学会分析算法的时间和空间复杂度。了解如何计算动态规划算法中dp数组的大小以及填充dp数组所需的时间。这有助于评估算法的效率,并且在实际应用中,可以根据问题的规模和要求选择合适的优化策略,如空间压缩技巧,以减少算法的空间复杂度。

总之,通过对动态规划问题的刷题学习,我不仅掌握了一种强大的算法设计技术,还培养了分析问题和解决问题的能力。希望这些经验能对其他同学在编程学习的道路上有所帮助,让大家在面对复杂的算法问题时能够更加从容自信。