动态规划算法:Java实现与实战指南
摘要
动态规划(Dynamic Programming)是解决复杂优化问题的核心技术。本文将使用Java语言全面介绍动态规划的实现方法,包括基础概念、经典问题解析、优化技巧以及实际工程应用场景。
1. 动态规划基础
1.1 斐波那契数列
public class Fibonacci {
// 基础递归实现
public static int fibRecursive(int n) {
if (n <= 1) return n;
return fibRecursive(n-1) + fibRecursive(n-2);
}
// 动态规划实现
public static int fibDP(int n) {
if (n <= 1) return n;
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
// 空间优化版本
public static int fibOptimized(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
public static void main(String[] args) {
System.out.println("递归实现 fib(10): " + fibRecursive(10));
System.out.println("DP实现 fib(10): " + fibDP(10));
System.out.println("优化DP fib(10): " + fibOptimized(10));
}
}
1.2 动态规划解题步骤
- 定义状态:明确dp数组的含义
- 状态转移方程:找出状态之间的关系
- 初始化:确定初始条件
- 计算顺序:确定计算方向
- 返回结果:获取最终解
2. 经典动态规划问题
2.1 0-1背包问题
public class Knapsack {
public static int knapsack01(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] dp = new int[n+1][capacity+1];
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i-1] <= w) {
dp[i][w] = Math.max(
dp[i-1][w],
values[i-1] + dp[i-1][w-weights[i-1]]
);
} else {
dp[i][w] = dp[i-1][w];
}
}
}
return dp[n][capacity];
}
// 空间优化版本
public static int knapsack01Optimized(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[] dp = new int[capacity+1];
for (int i = 0; i < n; i++) {
for (int w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], values[i] + dp[w-weights[i]]);
}
}
return dp[capacity];
}
public static void main(String[] args) {
int[] weights = {2, 3, 4, 5};
int[] values = {3, 4, 5, 6};
int capacity = 8;
System.out.println("0-1背包最大价值: " + knapsack01(weights, values, capacity));
System.out.println("优化版背包价值: " + knapsack01Optimized(weights, values, capacity));
}
}
2.2 最长公共子序列(LCS)
public class LongestCommonSubsequence {
public static int lcs(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]);
}
}
}
// 重构LCS
StringBuilder lcs = new StringBuilder();
int i = m, j = n;
while (i > 0 && j > 0) {
if (text1.charAt(i-1) == text2.charAt(j-1)) {
lcs.append(text1.charAt(i-1));
i--;
j--;
} else if (dp[i-1][j] > dp[i][j-1]) {
i--;
} else {
j--;
}
}
System.out.println("LCS序列: " + lcs.reverse().toString());
return dp[m][n];
}
public static void main(String[] args) {
String text1 = "ABCDGH";
String text2 = "AEDFHR";
System.out.println("LCS长度: " + lcs(text1, text2));
}
}
3. 路径与网格问题
3.1 不同路径问题
public class UniquePaths {
public static int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// 初始化第一行和第一列
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
// 空间优化版本
public static int uniquePathsOptimized(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j-1];
}
}
return dp[n-1];
}
public static void main(String[] args) {
System.out.println("不同路径数: " + uniquePaths(3, 7));
System.out.println("优化版路径数: " + uniquePathsOptimized(3, 7));
}
}
3.2 最小路径和
public class MinPathSum {
public static int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
for (int j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
public static void main(String[] args) {
int[][] grid = {
{1, 3, 1},
{1, 5, 1},
{4, 2, 1}
};
System.out.println("最小路径和: " + minPathSum(grid));
}
}
4. 字符串编辑距离
4.1 莱文斯坦距离
public class EditDistance {
public static int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m+1][n+1];
for (int i = 0; i <= m; i++) dp[i][0] = i;
for (int j = 0; j <= n; j++) dp[0][j] = j;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(dp[i-1][j],
Math.min(dp[i][j-1], dp[i-1][j-1])) + 1;
}
}
}
return dp[m][n];
}
// 空间优化版本
public static int minDistanceOptimized(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[] prev = new int[n+1];
for (int j = 0; j <= n; j++) prev[j] = j;
for (int i = 1; i <= m; i++) {
int[] curr = new int[n+1];
curr[0] = i;
for (int j = 1; j <= n; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
curr[j] = prev[j-1];
} else {
curr[j] = Math.min(prev[j],
Math.min(curr[j-1], prev[j-1])) + 1;
}
}
prev = curr;
}
return prev[n];
}
public static void main(String[] args) {
String word1 = "horse";
String word2 = "ros";
System.out.println("编辑距离: " + minDistance(word1, word2));
System.out.println("优化版编辑距离: " + minDistanceOptimized(word1, word2));
}
}
5. 股票买卖问题
5.1 单次交易
public class StockTrading {
public static int maxProfitOneTransaction(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
if (price < minPrice) {
minPrice = price;
} else if (price - minPrice > maxProfit) {
maxProfit = price - minPrice;
}
}
return maxProfit;
}
// 多次交易
public static int maxProfitMultipleTransactions(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i-1]) {
profit += prices[i] - prices[i-1];
}
}
return profit;
}
// 含冷冻期
public static int maxProfitWithCooldown(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int n = prices.length;
int[] hold = new int[n];
int[] unhold = new int[n];
hold[0] = -prices[0];
unhold[0] = 0;
for (int i = 1; i < n; i++) {
if (i == 1) {
hold[i] = Math.max(hold[i-1], -prices[i]);
} else {
hold[i] = Math.max(hold[i-1], unhold[i-2] - prices[i]);
}
unhold[i] = Math.max(unhold[i-1], hold[i-1] + prices[i]);
}
return unhold[n-1];
}
public static void main(String[] args) {
int[] prices = {7, 1, 5, 3, 6, 4};
System.out.println("单次交易最大利润: " + maxProfitOneTransaction(prices));
System.out.println("多次交易最大利润: " + maxProfitMultipleTransactions(prices));
int[] prices2 = {1, 2, 3, 0, 2};
System.out.println("含冷冻期最大利润: " + maxProfitWithCooldown(prices2));
}
}
6. 动态规划优化技巧
6.1 状态压缩
public class DPCompression {
// 斐波那契数列状态压缩
public static int fib(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
// 背包问题状态压缩
public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[] dp = new int[capacity+1];
for (int i = 0; i < n; i++) {
for (int w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], values[i] + dp[w-weights[i]]);
}
}
return dp[capacity];
}
public static void main(String[] args) {
System.out.println("斐波那契fib(10): " + fib(10));
int[] weights = {2, 3, 4, 5};
int[] values = {3, 4, 5, 6};
int capacity = 8;
System.out.println("压缩版背包价值: " + knapsack(weights, values, capacity));
}
}
6.2 斜率优化
import java.util.Deque;
import java.util.LinkedList;
public class SlopeOptimization {
// 滑动窗口最大值
public static int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < n; i++) {
// 移除不在窗口中的元素
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 移除比当前元素小的元素
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
public static void main(String[] args) {
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = maxSlidingWindow(nums, k);
System.out.println("滑动窗口最大值: " + Arrays.toString(result));
}
}
7. 实际工程应用
7.1 文本相似度计算
public class TextSimilarity {
public static double calculateSimilarity(String text1, String text2) {
int distance = editDistance(text1, text2);
int maxLen = Math.max(text1.length(), text2.length());
if (maxLen == 0) return 1.0;
return 1.0 - (double)distance / maxLen;
}
private static int editDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[] prev = new int[n+1];
for (int j = 0; j <= n; j++) prev[j] = j;
for (int i = 1; i <= m; i++) {
int[] curr = new int[n+1];
curr[0] = i;
for (int j = 1; j <= n; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
curr[j] = prev[j-1];
} else {
curr[j] = Math.min(prev[j],
Math.min(curr[j-1], prev[j-1])) + 1;
}
}
prev = curr;
}
return prev[n];
}
public static void main(String[] args) {
String text1 = "kitten";
String text2 = "sitting";
System.out.printf("文本相似度: %.2f\n", calculateSimilarity(text1, text2));
}
}
7.2 资源调度优化
public class ResourceScheduler {
public static int scheduleTasks(int[][] tasks, int[] resources) {
int n = tasks.length;
int m = resources.length;
// dp[i][j] 表示前i个任务使用前j个资源的最小成本
int[][] dp = new int[n+1][m+1];
for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE);
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 尝试将当前任务分配给每个资源
for (int k = 0; k <= j; k++) {
if (dp[i-1][j-k] != Integer.MAX_VALUE) {
int cost = (k > 0) ? calculateCost(tasks[i-1], resources[k-1]) : Integer.MAX_VALUE;
dp[i][j] = Math.min(dp[i][j], dp[i-1][j-k] + cost);
}
}
}
}
return dp[n][m];
}
private static int calculateCost(int[] task, int resource) {
return Math.abs(task[0] - resource) + task[1];
}
public static void main(String[] args) {
int[][] tasks = {{5, 2}, {3, 1}, {8, 3}};
int[] resources = {4, 6};
System.out.println("最小调度成本: " + scheduleTasks(tasks, resources));
}
}
8. 动态规划算法对比表
问题类型 | 时间复杂度 | 空间复杂度 | Java实现要点 |
---|---|---|---|
背包问题 | O(nW) | O(W) | 逆向遍历容量 |
LCS | O(mn) | O(min(m,n)) | 二维数组优化 |
LIS | O(n log n) | O(n) | 二分查找优化 |
编辑距离 | O(mn) | O(n) | 滚动数组 |
路径问题 | O(mn) | O(n) | 一维数组 |
股票问题 | O(n) | O(1) | 状态变量 |
总结
动态规划是解决优化问题的强大工具,Java实现时需要注意:
- 合理设计状态表示
- 优化空间复杂度
- 处理边界条件
- 使用合适的集合类型
- 考虑多线程环境下的安全性
"掌握动态规划,就是掌握了将复杂问题分解为简单子问题的艺术。Java的面向对象特性可以帮助我们更好地组织和封装DP算法。"