动态规划java

本文深入讲解了动态规划的基本概念、思想策略,并通过找零钱问题、走方格问题及最长公共序列问题等经典案例,详细介绍了如何应用动态规划解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基本概念

    动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

二、基本思想与策略

    基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

    与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)

以上都过于理论,还是看看常见的动态规划问题吧!!!

三、常见动态规划问题

   1、找零钱问题

   有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。
给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
测试样例:
[1,2,4],3,3
返回:2

解析:设dp[n][m]为使用前n中货币凑成的m的种数,那么就会有两种情况:

             使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]]

              不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n]>m。

        这样就可以求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n-1][m-peney[n]],否则,dp[n][m] = dp[n-1][m]

代码如下:

    <span style="font-size:18px;">import java.util.*;  
      
    public class Exchange {  
        public int countWays(int[] penny, int n, int aim) {  
            // write code here  
            if(n==0||penny==null||aim<0){  
             return 0;     
            }  
            int[][] pd = new int[n][aim+1];  
            for(int i=0;i<n;i++){  
             pd[i][0] = 1;     
            }  
            for(int i=1;penny[0]*i<=aim;i++){  
             pd[0][penny[0]*i] = 1;     
            }  
            for(int i=1;i<n;i++){  
                for(int j=0;j<=aim;j++){  
                    if(j>=penny[i]){  
                        pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];  
                    }else{  
                        pd[i][j] = pd[i-1][j];  
                    }  
                }  
            }  
            return pd[n-1][aim];  
        }  
    }</span>  

2、走方格问题

  有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100.
测试样例:
[[1,2,3],[1,1,1]],2,3
返回:4

解析:设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

代码如下:

    <span style="font-size:18px;">import java.util.*;  
      
    public class GoUpstairs {  
        public int countWays(int n) {  
            // write code here  
            if(n<=2)  
                return n;  
            int f = 1%1000000007;  
            int s = 2%1000000007;  
            int t = 0;  
            for(int i=3;i<=n;i++){  
             t = (f+s)%1000000007;  
             f = s;  
             s = t;  
            }  
           return t;   
        }  
    }</span>  

4、最长公共序列数

给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。
给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。
测试样例:
"1A2C3D4B56",10,"B1D23CA45B6A",12
返回:6

解析:设dp[n][m] ,为A的前n个字符与B的前m个字符的公共序列长度,则当A[n]==B[m]的时候,dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]),否则,dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);

代码如下:

    <span style="font-size:18px;">import java.util.*;  
      
    public class LCS {  
        public int findLCS(String A, int n, String B, int m) {  
            // write code here  
            int[][] dp = new int[n][m];  
            char[] a = A.toCharArray();  
            char[] b = B.toCharArray();  
           for(int i=0;i<n;i++){  
               if(a[i]==b[0]){  
                   dp[i][0] = 1;  
                   for(int j=i+1;j<n;j++){  
                       dp[j][0] = 1;  
                   }  
                   break;  
               }  
                 
           }  
             for(int i=0;i<m;i++){  
               if(a[0]==b[i]){  
                   dp[0][i] = 1;  
                   for(int j=i+1;j<m;j++){  
                       dp[0][j] = 1;  
                   }  
                   break;  
               }  
                 
           }  
           for(int i=1;i<n;i++){  
               for(int j=1;j<m;j++){  
                   if(a[i]==b[j]){  
                      dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]);  
                   }else{  
                       dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);  
                   }  
                         
               }  
           }   
              
            return dp[n-1][m-1];  
        }  
        public int max(int a,int b,int c){  
            int max = a;  
            if(b>max)  
                max=b;  
            if(c>max)  
                max = c;  
            return max;  
        }  
    }</span>  



### 动态规划Java 实现 动态规划是一种用于求解多阶段决策过程最优化问题的方法。它通过将复杂问题分解成更简单的子问题并保存这些子问题的结果,从而避免重复计算。以下是基于斐波那契数列的一个经典动态规划实现示例。 #### 斐波那契数列的动态规划实现 斐波那契数列是一个经典的例子,用来说明动态规划的思想。其定义如下: \[ F(n) = \begin{cases} 0 & n=0 \\ 1 & n=1 \\ F(n-1)+F(n-2) & n\geq 2 \end{cases} \] 下面是使用动态规划方法在 Java 中实现斐波那契数列的方式[^1]: ```java public class FibonacciDP { public static int fibonacci(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 void main(String[] args) { int n = 10; System.out.println("第 " + n + " 个斐波那契数是:" + fibonacci(n)); } } ``` 上述代码展示了如何利用一维数组 `dp` 来记录已经计算过的值,从而减少冗余运算。这种方法的时间复杂度为 \(O(n)\),空间复杂度也为 \(O(n)\)。 如果希望进一步降低空间复杂度,则可以仅保留两个变量来代替整个数组,这样可以使空间复杂度降至 \(O(1)\): ```java public class OptimizedFibonacciDP { public static int optimizedFibonacci(int n) { if (n <= 1) { return n; } int prev1 = 0, prev2 = 1; for (int i = 2; i <= n; i++) { int current = prev1 + prev2; prev1 = prev2; prev2 = current; } return prev2; } public static void main(String[] args) { int n = 10; System.out.println("第 " + n + " 个斐波那契数是:" + optimizedFibonacci(n)); } } ``` 此版本的空间效率更高,但仍保持时间复杂度不变。 --- #### § 1. 如何判断一个问题是否适合采用动态规划解决? 2. 动态规划中的重叠子问题特性是什么?如何识别这种特性? 3. 使用动态规划解决问题时,转移方程的设计有哪些技巧? 4. 是否可以用递归来替代动态规划?两者有何区别? 5. 如果需要处理更大的输入规模,应该如何改进现有的动态规划算法性能?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值