面试基础---面试刷题推荐 动态规划算法:背包问题与最长公共子序列

动态规划算法:背包问题与最长公共子序列

引言:动态规划的核心思想

动态规划(Dynamic Programming, DP)是一种解决复杂问题的算法思想,通过将问题分解为子问题,并保存子问题的解,避免重复计算,从而提高效率。本文将详细讲解动态规划在 背包问题最长公共子序列 中的应用,并提供易于记忆的代码模板。


一、背包问题

1.1 问题描述

给定 n 个物品,每个物品有一个重量 w[i] 和一个价值 v[i]。现在有一个容量为 C 的背包,如何选择物品放入背包,使得背包中的物品总价值最大?

示例

  • 输入:w = [2, 3, 4, 5], v = [3, 4, 5, 6], C = 8
  • 输出:10
  • 解释:选择物品 1 和物品 3,总重量为 2 + 5 = 7,总价值为 3 + 6 = 9

1.2 动态规划思路

1.2.1 状态定义
  • dp[i][j]:表示前 i 个物品在容量为 j 的背包中能获得的最大价值。
1.2.2 状态转移方程
  • 如果不选择第 i 个物品:dp[i][j] = dp[i - 1][j]
  • 如果选择第 i 个物品:dp[i][j] = dp[i - 1][j - w[i]] + v[i]
  • 最终状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
1.2.3 初始化
  • dp[0][j] = 0:没有物品时,价值为 0。
  • dp[i][0] = 0:背包容量为 0 时,价值为 0。

1.3 代码实现

1.3.1 Java 实现
public int knapsack(int[] w, int[] v, int C) {
    int n = w.length;
    int[][] dp = new int[n + 1][C + 1];

    // 初始化
    for (int i = 0; i <= n; i++) {
        dp[i][0] = 0;
    }
    for (int j = 0; j <= C; j++) {
        dp[0][j] = 0;
    }

    // 动态规划
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= C; j++) {
            if (j >= w[i - 1]) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }

    return dp[n][C];
}
1.3.2 代码讲解
  1. 初始化

    • dp[i][0] = 0:背包容量为 0 时,价值为 0。
    • dp[0][j] = 0:没有物品时,价值为 0。
  2. 动态规划

    • 遍历每个物品 i 和每种容量 j
    • 如果当前容量 j 可以容纳物品 i,则选择是否放入物品 i
    • 更新 dp[i][j] 为最大值。
  3. 返回结果

    • dp[n][C] 即为最大价值。

二、最长公共子序列(LCS)

2.1 问题描述

给定两个字符串 s1s2,找到它们的最长公共子序列的长度。

示例

  • 输入:s1 = "abcde", s2 = "ace"
  • 输出:3
  • 解释:最长公共子序列是 "ace",长度为 3。

2.2 动态规划思路

2.2.1 状态定义
  • dp[i][j]:表示 s1 的前 i 个字符和 s2 的前 j 个字符的最长公共子序列长度。
2.2.2 状态转移方程
  • 如果 s1[i - 1] == s2[j - 1]dp[i][j] = dp[i - 1][j - 1] + 1
  • 如果 s1[i - 1] != s2[j - 1]dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
2.2.3 初始化
  • dp[0][j] = 0s1 为空时,LCS 长度为 0。
  • dp[i][0] = 0s2 为空时,LCS 长度为 0。

2.3 代码实现

2.3.1 Java 实现
public int longestCommonSubsequence(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    int[][] dp = new int[m + 1][n + 1];

    // 动态规划
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s1.charAt(i - 1) == s2.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];
}
2.3.2 代码讲解
  1. 初始化

    • dp[0][j] = 0s1 为空时,LCS 长度为 0。
    • dp[i][0] = 0s2 为空时,LCS 长度为 0。
  2. 动态规划

    • 遍历 s1s2 的每个字符。
    • 如果字符相等,则 dp[i][j] = dp[i - 1][j - 1] + 1
    • 如果字符不等,则 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
  3. 返回结果

    • dp[m][n] 即为最长公共子序列的长度。

三、总结

动态规划是解决复杂问题的强大工具,通过将问题分解为子问题并保存子问题的解,可以显著提高效率。本文详细讲解了动态规划在 背包问题最长公共子序列 中的应用,并提供了易于记忆的代码模板。

3.1 背包问题

  • 状态定义dp[i][j] 表示前 i 个物品在容量为 j 的背包中的最大价值。
  • 状态转移dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])

3.2 最长公共子序列

  • 状态定义dp[i][j] 表示 s1 的前 i 个字符和 s2 的前 j 个字符的最长公共子序列长度。
  • 状态转移dp[i][j] = dp[i - 1][j - 1] + 1(字符相等)或 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])(字符不等)。

通过掌握动态规划的通用模板,可以轻松应对类似的复杂问题。希望本文的讲解和代码实现能帮助大家更好地理解和记忆动态规划算法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值