算法学习笔记1-动态规划问题(找钱问题)
问题一:换钱问题

以下各方法的示例为wallet=[5,3,2],aim=10,0<=pos<wallet.length
(1) 暴力递归法
Int F(wallet,pos,aim)
返回值:从pos位置的钱币开始,共有X种方式可以找aim元。

思路:
暴力递归法的思路主要是对于当前的aim和可使用的钱数wallet[pos],轮流调用其所有的可能方式。每一种方式,返回的是它后续可以找aim元的组合类型。
示例:
例如:最开始时pos=0,aim=10,当前货币为wallet[pos=0]=5元。所以共有0张,1张,2张5元的组合,进行遍历。
选择0张5元后,对应货币为3元。可以有0张,1张,2张,3张3元的组合方式。
选择1张5元后,对应货币为3元。可以有0张,1张3元的组合方式。
选择2张5元后,直接返回1,表示成功。
然后像这样层层递归下去。
复杂度分析:
这里的复杂度分析比较麻烦,设wallet[N],aim=A。需要思考一下,以后再写。
(2) 优化一:记忆化搜索
思路:
主要是看到不同路径中可能有相同的子路径,例如[3,0][3,1][3,2]等,这些每次都要反复的调用递归函数,如果可以将这些相同的子路径返回值记录下来,就会节省一定的时间。
因此建立一个Map,调用子路径时先看Map里面有没有存在的,有则取出,没有则继续调用递归函数。
(3) 优化二:动态规划-二维表方法
思路:
解空间是一张二维表dp,横坐标为0àaim_max,纵坐标为wallet[0]àwallet[length-1]。其中dp[0,aim]是最终的返回值,dp[wallet.length+1][0]=1,这都是根据递归中的截止条件可以确定的。
而dp中每一个位置:
dp[pos][aim]=dp[pos+1][aim-wallet[pos]*i],其中(aim-wallet[pos]*i>=0)
示例:
初始解空间表
|
Pos |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
0 |
|
|
|
|
|
|
|
|
|
|
Res |
|
1 |
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
3 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
解空间表1,pos=2时,dp[2][k]=dp[3][k]+dp[3][k-2]+dp[3][k-4]+…+dp[3][k-2*i](k-2i>=0)。
|
Pos |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
0 |
|
|
|
|
|
|
|
|
|
|
Res |
|
1 |
|
|
|
|
|
|
|
|
|
|
|
|
2 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
|
3 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
解空间表2,pos=1时,dp[1][k]=dp[2][k]+dp[2][k-3]+dp[2][k-6]+…+dp[2][k-3*i](k-3i>=0)。
|
Pos |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
0 |
|
|
|
|
|
|
|
|
|
|
Res |
|
1 |
1 |
0 |
1 |
1 |
1 |
1 |
2 |
1 |
2 |
2 |
2 |
|
2 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
|
3 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
解空间表3,pos=0时,dp[0][k]=dp[1][k]+dp[1][k-5]+dp[1][k-10]+…+dp[1][k-5*i](k-5i>=0)。
|
Pos |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
0 |
1 |
0 |
1 |
1 |
1 |
2 |
2 |
2 |
3 |
3 |
4 |
|
1 |
1 |
0 |
1 |
1 |
1 |
1 |
2 |
1 |
2 |
2 |
2 |
|
2 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
|
3 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
(4) 优化三:动态规划-一维表方法
思路:
可以看到,任意一个位置的返回值为
∑dp[pos+1][aim-wallet[pos]*i]=dp[pos+1][aim]+dp[pos][aim-wallet[pos]]
也就是说,每一个位置的返回值=正下方的那个位置的返回值+同一行它之前wallet[pos]位置处的值。
这也就是说,如果用一维列表来存储的话,每个位置的值等于它原来的值加上它前面某个位置的值,并不依赖于其他的东西。因而这类问题可以通过一维数组推导。
示例:
Int[] dp=new int[10+1]
初始时
|
Pos=3 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
一次迭代,dp[k]=dp[k]+k-2>=0?dp[k-2]:0;
|
Pos=2 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
二次迭代,dp[k]=dp[k]+k-3>=0?dp[k-3]:0;
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
Pos=2 |
1 |
0 |
1 |
1 |
1 |
1 |
2 |
1 |
2 |
2 |
2 |
三次迭代,dp[k]=dp[k]+k-5>=0?dp[k-5]:0;
|
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
Pos=2 |
1 |
0 |
1 |
1 |
1 |
2 |
2 |
2 |
3 |
3 |
4 |
代码如下
import java.util.HashMap;
public class DP_MoneyTypesProblem {
/*
这个方法是正确的递归方法
*/
public static int combination2(int[] wallet, int pos,int aim)
{
System.out.println("pos:"+pos+",aim:"+aim);
int sum=0;
if(pos==wallet.length)
{
sum=aim==0?1:0;
}
else
{
for(int i=0;wallet[pos]*i<=aim;i++) {
sum += combination2(wallet,
pos + 1,
aim - i * wallet[pos]);
}
}
System.out.println("sum:"+sum);
return sum;
}
/*
记忆化搜索,带缓存的方法
*/
// KEY: pos_aim
//VALUE:返回值
public static HashMap<String,Integer> record;//Integer与int的区别
public static int combination3(int[] wallet, int pos,int aim)
{
System.out.println("pos:"+pos+",aim:"+aim);
int sum=0;
if(pos==wallet.length)
{
sum=aim==0?1:0;
}
else
{
for(int i=0;wallet[pos]*i<=aim;i++) {
String name=Integer.toString(pos+1)+"_"+Integer.toString(aim-i*wallet[pos]);
if(record.containsKey(name))
sum+=record.get(name);
else {
int temp = combination3(wallet, pos + 1,
aim - i * wallet[pos]);
sum += temp;
record.put(name, temp);
}
}
}
System.out.println("sum:"+sum);
return sum;
}
/*
*下面是动态规划-二维矩阵方法
*/
public static int combination4(int[] wallet,int aim)
{
if(wallet==null||aim<0)
return -1;
//初始化二维矩阵
int[][] dp=new int[wallet.length+1][aim+1];
//设置矩阵的初始值,将所有可直接确定取值的位置赋值
for(int i=0;i<wallet.length+1;i++)
dp[i][0]=1;
for(int i=1;i<aim+1;i++)
dp[wallet.length][i]=0;
for(int i=wallet.length-1;i>=0;i--)
{
for(int j=0;j<aim+1;j++)
{
if(j-wallet[i]>=0)
dp[i][j]=dp[i+1][j]+dp[i][j-wallet[i]];
else
dp[i][j]=dp[i+1][j];
}
}
for(int i=0;i<wallet.length+1;i++)
{
for(int j=0;j<aim+1;j++)
System.out.print(dp[i][j]+" ");
System.out.println("");
}
return
dp[0][aim];
}
/*
*
* 下面是动态规划-一维表方法
*/
public static int combination5(int[] wallet,int aim)
{
if(wallet==null||aim<0)
return -1;
//初始化一维矩阵
int[] dp=new int[aim+1];
//设置矩阵的初始值,将所有可直接确定取值的位置赋值
dp[0]=1;
for(int i=1;i<aim+1;i++)
dp[i]=0;
for(int i=wallet.length-1;i>=0;i--)
{
for(int j=0;j<aim+1;j++)
{
if(j-wallet[i]>=0)
dp[j]=dp[j]+dp[j-wallet[i]];
}
}
for(int j=0;j<aim+1;j++)
System.out.print(dp[j]+"
");
System.out.println("");
return dp[aim];
}
public static void main(String[] args)
{
/*
* 此类问题首先是递归法
* 优化一是增加缓存,使用前提是无后效性问题(面试常见)/有后效性问题(N皇后问题)
*
* */
int wallet[]=new
int[3];
//以下是示例一:wallet=[200,100,50],aim=1000
/*
wallet[1]=100;
wallet[0]=200;
wallet[2]=50;
int res=0;
//下面是正确的递归方法
res=combination2(wallet,0,1000);
System.out.println(res);
System.out.println("====================================");
//下面是带缓存的方法
record=new HashMap<String,
Integer>();
int res2=combination3(wallet,0,1000);
System.out.println(res2);
//下面是动态规划-二维表方法
int
res3=combination4(wallet,1000);
System.out.println(res3);
//下面是动态规划-一维表方法
int
res4=combination5(wallet,1000);
System.out.println(res4);
*/
System.out.println("Example 2
====================================");
//以下是示例二:wallet=[5,3,2],aim=10
wallet[0]=5;
wallet[1]=3;
wallet[2]=2;
//下面是正确的递归方法
int res=combination2(wallet,0,10);
System.out.println(res);
System.out.println("====================================");
//下面是带缓存的方法
record=new HashMap<String, Integer>();
int res2=combination3(wallet,0,10);
System.out.println(res2);
//下面是动态规划-二维表方法
int res3=combination4(wallet,10);
System.out.println(res3);
//下面是动态规划-一维表方法
int res4=combination5(wallet,10);
System.out.println(res4);
}
}
浙公网安备 33010602011771号