题目:
给定一个数组arr,arr中所有的值均为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有几种方法?
一、暴力搜索方法
一个案例:
arr={5,10,25,1} aim = 1000
解法思路:
1.先用0个5,让[10,25,1]组下剩下的1000,最终的方法数记为 res1
2.用一个5,让[10,25,1]组下剩下的995,最终的方法数记为 res2
3.用2个5,让[10,25,1]组下剩下的990,最终的方法数记为 res3
…
201.用200个5,让[10,25,1]组下剩下的0,最终的方法数记为res201
所有res相加即为最终的结果数
这显然是一个可以使用暴力递归解决的问题,定义递归函数: int p1(arr, index, aim)它的含义是如果使用arr(index…N-1)这些面值的钱组成aim,返回的总方法数,具体代码如下:
public int coins1(int[] arr, int aim) {
if(arr == null || arr.length == 0 || aim < 0) {
return 0;
}
return process1(arr, 0, aim);
}
private process1(int[]arr, int index, int aim) {
int res = 0;
if(index == arr.length) {
res = aim == 0 ? 1 : 0;
} else {
for(int i =0; arr[index] * i < = aim; i++) {
res += process1(arr, index + 1, aim - arr[index] * i);
}
}
}
以上代码为暴力递归方法,暴力之所以暴力就是因为这段代码中存在着很多重复的计算,比如,当我们想要使用0张5元和1张10元时剩下的aim = 990时的方法与我们此刻使用2张5元和0张10元时所对应aim时的方法其实应该是同一回事(也就是只去求p1(arr, 2, 990)的返 回值),但是暴力递归便会重复计算。这种其实在保留递归中非常常见,因此我们就需要进行优化暴力递归的算法。
二、记忆搜索的方法
使用记忆搜索的方法的时候,我们使用的题目的场景还是上面的场景。
但是去分析递归函数p1(arr, index, aim)的时候,我们发现其实arr是始终不变的,变化的只有index和aim的值,因此我们递归方法可以变为p(index, aim),大量重复计算之所以会发生其实就是很多重复递归的结果并没有记录下来,所以对于同样的实参传过来我们还得 重复去求,所以我们可以事先准备好一张map表,把index和aim组成的共同结果组成key,返回结果为value放到map中,在下次进行递归之前,我们现在这张hash表中查询这个递归过程是否已经计算过了,如果计算过了那么我直接从这个hash表里面直接取值就ok,如果这 个key不存在我再去进行递归求值就可以
代码如下:
public int coins2(int [] arr, int aim) {
if(arr == null || arr.length == 0 || aim < 0) {
return 0;
}
int [] [] map = new int[arr.length + 1][aim + 1];
return process2(arr, 0, aim, map);
}
private int process2(int [] arr, int index, int aim, int [][] map) {
int res = 0;
if(index == arr.length) {
res = aim == 0 ? 1 : 0;
} else {
int mapValue = 0;
for(int i = 0; arr[index] * i < aim; i++) {
mapValue = map[index + 1][aim - arr[index] * i];
if(mapValue != 0){
res += mapValue == -1 ? 0 : mapValue;
} else {
res += process2(arr, index + 1, aim - arr[index] * i, map);
}
}
}
map[index][aim] = res == 0 ? -1 : res;
return res;
}