动态规划算法:Java实现与实战指南

小编

关注

阅读 1

13小时前

动态规划算法: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 动态规划解题步骤

  1. 定义状态:明确dp数组的含义
  2. 状态转移方程:找出状态之间的关系
  3. 初始化:确定初始条件
  4. 计算顺序:确定计算方向
  5. 返回结果:获取最终解

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实现时需要注意:

  1. 合理设计状态表示
  2. 优化空间复杂度
  3. 处理边界条件
  4. 使用合适的集合类型
  5. 考虑多线程环境下的安全性

"掌握动态规划,就是掌握了将复杂问题分解为简单子问题的艺术。Java的面向对象特性可以帮助我们更好地组织和封装DP算法。"

精彩评论(0)

0 0 举报