CodeWalk

动态规划:背包问题和最长公共子序列的Java实现

作者:我是大山 · 2026-05-30 12:55

请用Java实现两种经典的动态规划问题:0-1背包问题最长公共子序列(LCS)。详细解释DP的状态定义、状态转移方程、初始化和空间优化技巧。动态规划的核心思想(最优子结构+重叠子问题)是什么?

回答

我是大山

0-1背包问题

问题:N个物品,每个物品重量w[i]、价值v[i],背包容量W,求能装入的最大价值。

状态定义dp[i][j] = 前i个物品,容量j的最大价值

转移方程dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])

public class Knapsack {
    // 二维DP
    public int knapsack(int W, int[] w, int[] v) {
        int n = w.length;
        int[][] dp = new int[n + 1][W + 1];
        
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= W; 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][W];
    }
    
    // 空间优化(一维数组,逆序遍历)
    public int knapsackOptimized(int W, int[] w, int[] v) {
        int[] dp = new int[W + 1];
        for (int i = 0; i < w.length; i++) {
            // 逆序保证每个物品只取一次
            for (int j = W; j >= w[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
        return dp[W];
    }
}

最长公共子序列(LCS)

问题:求两个字符串的最长公共子序列(不要求连续)。

状态定义dp[i][j] = text1[0..i-1]和text2[0..j-1]的LCS长度

转移方程

  • text1[i-1] == text2[j-1]dp[i][j] = dp[i-1][j-1] + 1
  • 否则:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
public class LCS {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                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];
    }
    
    // 回溯:还原LCS字符串
    public String backtrack(String text1, String text2, int[][] dp) {
        int i = text1.length(), j = text2.length();
        StringBuilder sb = new StringBuilder();
        while (i > 0 && j > 0) {
            if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                sb.append(text1.charAt(i - 1));
                i--; j--;
            } else if (dp[i - 1][j] > dp[i][j - 1]) {
                i--;
            } else {
                j--;
            }
        }
        return sb.reverse().toString();
    }
}

DP核心思想

概念说明背包问题LCS
最优子结构子问题最优解构成全局最优容量W的最佳选择包含容量W-w[i]的最佳选择text1[0..i]和text2[0..j]的LCS由更短的子串LCS组成
重叠子问题子问题被重复计算dp[i-1][j]被多次使用dp[i-1][j]和dp[i][j-1]被多次计算
无后效性子问题的解不影响其他子问题的解已确定的前i个物品不影响后续选择已匹配的前缀不影响后续匹配