DP-java版本
来源:互联网 发布:淘宝店铺如何上传宝贝 编辑:程序博客网 时间:2024/05/16 14:15
常见dp问题的java叙述总结。
背包问题
0-1背包
对于0-1背包问题,运用dp的思想可以有两种常见状态转移:认为物品下标从0开始
dp[i][j]
表示从前i-1个物品选,在重量不超过j的情况下的最大价值。(正向)dp[i][j]
表示从第i个物品开始选,在重量不超过j的情况下的最大价值。(反向)
状态转移方程分别如下:
#初始化dp[0][j]=0if j<w[i]: dp[i+1][j]=dp[i][j]else: dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i])
#初始化dp[n][j]=0if j<w[i]: dp[i][j]=dp[i+1][j]else: dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i])
完全背包
这里运用正向dp思路:
dp[i][j]
表示从前i-1个物品选,在重量不超过j的情况下的最大价值。
转移方程如下:
#初始化dp[0][j]=0dp[i+1][j]=max(dp[i][j],dp[i][j-k*w[i]]+k*v[i]) #(k>=0且k*w[i]<=j)
这是最容易想到的思路,但是实际上还可以优化,因为其中有重复计算:
比如在计算dp[i+1][j]
的计算中(k>=1),与在d[i+1][j-w[i]]
的计算中选择k-1的情况相同。
即有以下变形:
dp[i+1][j]=max(dp[i][j-k*w[i]]+j*v[i])(k>=0)=max(dp[i][j],max(dp[i][j-k*w[i]]+k*v[i]))(k>=1)=max(dp[i][j],max(dp[i][j-w[i]-k*w[i]]+k*v[i]+v[i]))(k>=0)=max(dp[i][j],dp[i+1][j-w[i]]+v[i])
所以时间复杂度降为平方级。
package DP;/** * @author prime on 2017/5/1. */import java.util.Arrays;public class KnapSack{//各种背包 private static int solve0_1(int[] v,int[] w,int c) {//正向dp解0-1背包 int n=v.length; int[][] dp=new int[n+1][c+1];//从前i个物品中选出总重量不超过j的物品时总价值的最大值 Arrays.fill(dp[0],0);//dp[0][j]置0 for (int i=0;i<n;i++) for (int j=0;j<=c;j++) { if (j<w[i]) dp[i+1][j]=dp[i][j]; else dp[i+1][j]=Math.max(dp[i][j],dp[i][j-w[i]]+v[i]); } return dp[n][c]; } private static int solve0_1_reverse(int[] v,int[] w,int c) {//反向dp解0-1背包 int n=v.length; int[][] dp=new int[n+1][c+1];//从第i个物品开始选,在不超过j的条件下的最大价值(i从0开始) Arrays.fill(dp[n],0);//dp[n][j]置0 for (int i=n-1;i>=0;i--) for (int j=0;j<=c;j++) { if (j<w[i]) dp[i][j]=dp[i+1][j]; else dp[i][j]=Math.max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]); } return dp[0][c]; } private static int[][] dp=new int[6][101]; private static int[] w={10,20,30,40,50}; private static int[] v={20,30,65,40,60}; private static int n=5; private static int solve0_1_rec(int i,int j)//从第i个物品开始选,在不超过j的条件下的最大价值(i从0开始) {//反向dp的递归版本 int res; if (dp[i][j]>0)//备忘录,没有这个就是BS return dp[i][j]; if (i==n) return 0; else if (j<w[i]) res=solve0_1_rec(i+1,j); else res=Math.max(solve0_1_rec(i+1,j),solve0_1_rec(i+1,j-w[i])+v[i]); return dp[i][j]=res; } private static int solve(int[] v,int[] w,int c) {//完全背包问题,正向dp思考,优化后 int n=v.length; int[][] dp=new int[n+1][c+1]; Arrays.fill(dp[0],0);//dp[0][j]置0 for (int i=0;i<n;i++) for (int j=0;j<=c;j++) { if (j<w[i]) dp[i+1][j]=dp[i][j]; else dp[i+1][j]=Math.max(dp[i][j],dp[i+1][j-w[i]]+v[i]); } return dp[n][c]; } private static int solve_bad(int[] v,int[] w,int c) { int n=v.length; int[][] dp=new int[n+1][c+1]; Arrays.fill(dp[0],0);//dp[0][j]置0 for (int i=0;i<n;i++) for (int j=0;j<=c;j++) for (int k=0;k*w[i]<=j;k++) { dp[i+1][j]=Math.max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]); } return dp[n][c]; } public static void main(String[] args) { int[] w={10,20,30,40,50}; int[] v={20,30,65,40,60}; System.out.println(solve0_1(v,w,100)); System.out.println(solve0_1_reverse(v,w,100)); System.out.println(solve0_1_rec(0,100)); System.out.println(solve(v,w,100)); System.out.println(solve_bad(v,w,100)); }}
换钱问题
dp[i][j]
表示可以随便使用0..i的纸币的情况下,组成j元所需要的最少纸币数。
初始化都是相同的,即第一列dp[i][0]
都是0;而第一行dp[0][j]
,如果可以被第一个货币整除就填入结果即货币数(对于第二种纸币只有一张的情况下只有等于第一个纸币才填1),否则指定一个非常大的数(最好不要用整数最大值,可能会溢出)
无限纸币
这种情况下和完全背包问题很相似:
if arr[i]>j: #当前货币太大了 dp[i][j]=dp[i-1][j]else: dp[i][j]=min(dp[i-1][j],dp[i][j-arr[i]]+1)
这里的也是像上面完全背包问题一样,化简后的结果。原始递推公式是:
if arr[i]>j: #当前货币太大了 dp[i][j]=dp[i-1][j]else: dp[i][j]=min(dp[i][j*k*arr[i]]+k)(k>=0)
这里对它用上面一样的方法化简即可。
单一纸币
这种就相对容易些:
if arr[i]>j: #当前货币太大了 dp[i][j]=dp[i-1][j]else: dp[i][j]=min(dp[i-1][j],dp[i-1][j-arr[i]]+1)
代码如下:
package DP;/** * 换钱问题的DP求解 */import java.util.Arrays;import java.util.Scanner;public class money{ private static int minCoin1(int[] arr,int aim) {//每种纸币无限制的换钱问题 int n=arr.length; int[][] dp=new int[n][aim+1];//dp[i][j]表示在可以任意使用0..i的货币的情况下,组成j所需要的最小张数 for (int i=0;i<n;i++)//第一列显然是0 dp[i][0]=0; for (int j=1;j<=aim;j++) { if (j%arr[0]==0) dp[0][j]=j/arr[0]; else dp[0][j]=20000;//不用整数最大值是为了防止溢出。 } /*以上是初始化部分*/ for (int i=1;i<n;i++) for (int j=0;j<=aim;j++) { if (j<arr[i])//第i个钱太大了 dp[i][j]=dp[i-1][j]; else dp[i][j]=Math.min(dp[i-1][j],dp[i][j-arr[i]]+1); } return dp[n-1][aim]!=20000?dp[n-1][aim]:-1; } private static int minCoin2(int[] arr,int aim) {//每种纸币只能用一次 int n=arr.length; int[][] dp=new int[n][aim+1];//dp[i][j]表示任意使用0..i的纸币,组成j元的最小张数 for (int i=0;i<n;i++) dp[i][0]=0; for (int j=1;j<=aim;j++) { if (j==arr[0]) dp[0][j]=1; else dp[0][j]=20000; } /*初始化完成*/ for (int i=1;i<n;i++) for (int j=0;j<=aim;j++) { if (arr[i]>j) dp[i][j]=dp[i-1][j]; else dp[i][j]=Math.min(dp[i-1][j],dp[i-1][j-arr[i]]+1); } return dp[n-1][aim]!=20000?dp[n-1][aim]:-1; } public static void main(String[] args) { int[] a={5,7,25,50}; System.out.println(minCoin1(a,15)); System.out.println(minCoin2(a,15)); }}
换钱的方法数问题(每种货币无限)
三种方法:暴力搜索、记忆化搜索、DP
暴力搜索
暴力搜索基于以下事实:
假设货币数组arr[0..n-1],目标aim,则过程如下:
- 不用arr[0],用剩下的arr[1..n-1]组成aim
- 用一张arr[0],用剩下的arr[1..n-1]组成aim-arr[0]
- 用两张arr[0],用剩下的arr[1..n-1]组成aim-2*arr[0]
- ……
所以,就可以写出一个递归的暴力搜索方法:
private static int coin1(int[] arr,int aim) {//暴力搜索 if (arr==null||aim<0||arr.length==0) return 0; return BS1(arr,0,aim); } private static int BS1(int[] arr,int index,int aim) {/*BS1表示如果用arr[index..]组成aim元的方法数*/ int res=0; if (index==arr.length) res=aim==0?1:0; else { for (int i=0;i*arr[index]<=aim;i++) res+=BS1(arr,index+1,aim-i*arr[index]); } return res; }
暴力搜索最大的弊端在于有很多重复计算,比如用2张5元和一张10元而言,后序的递归都是一样的情形。为此想保留每次计算的结果,有了下面的优化:
记忆化的暴力搜索
把上述BS1的参数和返回值对应起来,并保存到二维数组中:
private static int coin2(int[] arr,int aim) {/*记忆后的暴力搜索*/ if (arr==null||arr.length==0||aim<0) return 0; int[][] memo=new int[arr.length+1][aim+1]; for (int[] e:memo) Arrays.fill(e,-1); return BS2(arr,0,aim,memo); } private static int BS2(int[] arr,int index,int aim,int[][] memo) {/*基本参数都和上面一样,memo[i][j]表示index为i,aim为j时的计算结果*/ int res=0; if (index==arr.length) res=aim==0?1:0; else { if (memo[index][aim]!=-1) return memo[index][aim]; for (int i=0;i*arr[index]<=aim;i++) res+=BS2(arr,index+1,aim-i*arr[index],memo); } return memo[index][aim]=res; }
初始化memo为-1表示还没被计算,当后序程序递归时,查表即可。
非最优的动态规划
dp和记忆化搜索本质上是一样的,都是空间换时间,以某种方式 进行记录。不同的地方在于dp规定计算顺序,后面的依赖前面的结果;而记忆化搜索只是单纯记录中间结果,对顺序没有规定。
private static int coin3(int[] arr,int aim) {//dp法 int n=arr.length; /*dp[i][j]表示使用0..i的货币组成j元的方法数*/ int[][] dp=new int[n][aim+1]; /*第一列显然都是1*/ for (int i=0;i<n;i++) dp[i][0]=1; for (int j=0;j<=aim;j++) { if (j%arr[0]==0) dp[0][j]=1;//能被找开就说明是一种方法 } for (int i=1;i<n;i++) for (int j=0;j<=aim;j++) { int count=0; for (int k=0;k*arr[i]<=j;k++) count+=dp[i-1][j-k*arr[i]]; dp[i][j]=count; } return dp[n-1][aim]; }
最优DP
上面的dp之中还有很多重复计算,利用推导可以化简如下:
dp[i][j]=dp[i-1][j-arr[i]*k] (k>=0)=dp[i-1][j]+dp[i-1][j-arr[i]-arr[i]*k](k>=0)=dp[i-1][j]+dp[i][j-arr[i]]
由此有以下代码:
private static int coin4(int[] arr,int aim) {//最优dp int n=arr.length; /*dp[i][j]表示使用0..i的货币组成j元的方法数*/ int[][] dp=new int[n][aim+1]; /*第一列显然都是1,组成0元只有一种方法*/ for (int i=0;i<n;i++) dp[i][0]=1; /*第一行如果可以被找开就是1*/ for (int j=1;j<=aim;j++) if (j%arr[0]==0) dp[0][j]=1; for (int i=1;i<n;i++) for (int j=0;j<=aim;j++) { if (j<arr[i]) dp[i][j]=dp[i-1][j]; else dp[i][j]=dp[i-1][j]+dp[i][j-arr[i]]; } return dp[n-1][aim]; }
- DP-java版本
- Java版本
- Java--版本
- Java版本的版本变迁
- Java版本的版本变迁
- 树形dp 二叉树版本与多叉树版本
- 拦截导弹 dp java
- Java版本名称趣谈
- 趣谈Java版本名称
- 趣谈Java版本名称
- Java的版本问题
- java 版本含义
- java中的版本问题
- java版本爬虫
- java各版本名称
- Java的版本
- JAVA版本区分
- java 的版本异常
- N分之一 竖式除法模拟
- 饭卡 01背包 + 贪心
- poj 1088 滑雪 DP(dfs的记忆化搜索)
- leetcode题目分类/总结
- 2440超详细uboot移植笔记(二)------新建单板
- DP-java版本
- Billboards 技术在Unity 中的几种使用方法
- Laravel之文件上传
- POJ265——Area(Pick公式)
- 第一篇
- 哈尔滨火车站下面有三个火车票代售点,假如哈尔滨到北京的火车票总共是200张,如何用程序来实现三个售票点同时卖票的功能。
- java执行bat文件
- Windows下编译X264,VS2015运行
- CC2640之广播MAC地址