【Leetcode】换钱的方法数(暴力递归,动态规划,记忆化搜搜,路径压缩)

题目

给定数组arr,arr中所有的值都是整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求换钱有多少种方法数?

分析

这一题也是递归搜索的套路题,复习 机器人到达指定位置方法数

首先可以通过递归搜索得到递归解法,复杂度比较大;

其次通过记忆化搜索和动态规划进行优化;

最后动态规划还能继续优化。

解法1 暴力递归

每张面值尝试不同的张数,如果arr=[5,10,25,1],aim=1000分析过程如下:

  1. 用0张5元的货币,[10,25,1]组成剩下1000
  2. 用1张5元的货币,[10,25,1]组成剩下的995
  3. 用2张5元的货币,[10,25,1]组成剩下的900

  1. 用200张5元的货币,[10,25,1]组成剩下的0

将上面201种组合方法数加起来,就是总的结果。

//暴力递归
int process(int index, int arr[], int aim,int length){
    int res=0;

    if(index==length){
        res= aim==0?1:0;
    }
    else {
        for (int i = 0; arr[index] * i <= aim; i++) {
            res += process(index + 1, arr, aim - arr[index] * i,length);
        }
    }
    return res;
}

解法2 记忆化搜索

暴力递归中存在大量的重复计算,暴力递归的时间复杂度最差情况下是O(aim^N)

记忆化搜索就是实现准备一个map,将所有已经计算好的结果存储到map中,下次运到的时候再直接取出来,避免重复计算。

//记忆化搜索
int dict[arr.length+1][aim+1]; //dict[index][aim]
int process1(int index, int arr[], int aim, int length){
    int res =0;

    if(index == length){
        res = aim ==0 ? 1: 0;
    }
    int dictValue = 0;
    for(int i=0;arr[index]*i<=aim;i++){
        dictValue = dict[index][aim-arr[index]*i];
        if(dictValue!=0) res += dictValue == 0 ? -1 : dictValue;
        else{
            res+= process1(index+1, arr, aim-arr[index]*i,length);
        }
    }
    dict[index][aim]= res == 0 ? -1 : res;
    return res;

}

记忆化搜索是针对暴力递归的最初级的优化手段,分析递归函数的状态可以由哪些变量表示,做出相应维度的map进行记录即可。

解法3 动态规划

根据 机器人到达指定位置方法数 中如何将暴力递归优化为动态规划的套路,思考dp(i,j)由什么转化而来,可以想到:

  • 完全不用arr[i],只使用arr[0…i-1]货币时,方法数为dp[i-1][j]
  • 用1张arr[i], 剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-arr[i]
  • 用2张arr[i],剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-2*arr[i]]

  • 用k张arr[i],剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-k*arr[i]]

最终dp[N-1][aim]就是最终结果。

int num =0;
for(int i=1; i <arr.length(); i++){
	for(int j=1;j<= aim; j++){
		num = 0;
		for( int k=0; j-arr[i]*k >=0; k++){
			num+= dp[i-1][j-arr[i]*k];
	}
	dp[i][j]=num;
}

解法4 动态规划优化

  • 完全不用arr[i],只使用arr[0…i-1]货币时,方法数为dp[i-1][j]
  • 用1张arr[i], 剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-arr[i]
  • 用2张arr[i],剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-2*arr[i]]

  • 用k张arr[i],剩下的用arr[0…i-1]货币时,方法数为dp[i-1][j-k*arr[i]]

在这一步我们简单分析可以发现,除了第1行,剩下的累加

dp[i-1][j-arr[i]] + dp[i-1][j-2arr[i]] + … + dp[i-1][j-karr[i]]

的结果等于 dp[i][j-arr[i]]

那么 dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]

时间复杂度为 O(N*aim)

解法5 路径压缩

动态规划路径压缩参考 矩阵的最小路径和

for( int i=1 ; i < arr.length(); i++){
	for(int j=1; j<= aim ; j++){
		dp[j] += j-arr[i] >=0? dp[j-arr[i]] : 0;
	}
}

时间复杂度为O (N*aim)

空间复杂度为O(aim)

总结

这是一个比较简单的题,但是可以联系起很多的暴力递归的优化方法,包括记忆化搜索,动态规划,动态规划路径压缩。同时记忆化搜索虽然跟动态规划相似的时间复杂度,但是记忆化是建立在递归上的,是一种剪枝,但仍然需要大量的栈调用;而动态规划是事先规定好了计算顺序,两者各有优缺点。

review

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值