LeetCode刷题记录----322.零钱兑换(Medium)
2025/9/21
题目(Medium):
322.零钱兑换
我的思路:
模式识别:要凑到11块,那就要先凑到10块(获取其他更小的)-> 子问题和主问题有相同的结构,动态规划或者记忆化深度优先搜索。
思路:因为是动态规划,我们需要先设定好对应的状态dp[i]的含义以及对应的状态转移方程
dp[i]:意味着要凑到金额 i 需要的最少硬币数
状态转移方程:dp[i] = min(dp[i], dp[coin] + dp[i - coin])
因此,大致的步骤如下:
①先初始化一个amount + 1长度,默认值为int.MaxValue(最大值)的dp数组
②接着遍历一次硬币数组,把遍历到的硬币面值小于amount的,设置对应dp[coin] = 1;如果发现有coin == amount 直接返回 1
③接着从1遍历到amount的数字,对于每个数字,我们都再遍历一次硬币数组,在这里面利用状态转移方程计算当前的状态值
④最后如果返回的金额值状态对应还是int.MaxValue,那就返回-1;否则返回dp[amount]
具体代码如下:
public class Solution {public int CoinChange(int[] coins, int amount) {//dp[i] 是 凑到i元所需的最小硬币数量if(amount == 0)return 0;int[] dp = new int[amount+1];Array.Fill(dp, int.MaxValue);dp[0] = 0;foreach(int coin in coins){if(coin < amount)dp[coin] = 1;else if(coin == amount)return 1;}for(int i = 1; i <= amount; i++){foreach(int coin in coins){//当前硬币小于当前要得到的总金额,同时它的另一个加数的金额也是可以凑得到的时候if(coin <= i && dp[i-coin] != int.MaxValue)dp[i] = Math.Min(dp[i], dp[coin] + dp[i - coin]);}}return dp[amount] == int.MaxValue ? -1 : dp[amount];}
}
时间复杂度:O(N * Amount)
空间复杂度:O(Amount)
优化思路:
对于状态转移方程,其实我们知道,只要coin <= amount,那dp[coin]肯定是 == 1的。
因此我们之前写的状态转移方程:
可以优化写成:dp[i] = min(dp[i], dp[i - coin] + 1)
这样我们也不用一开始还要遍历一遍硬币数组来把对应的dp[coin]设置为1了
具体代码如下:
public class Solution {public int CoinChange(int[] coins, int amount) {//dp[i] 是 凑到i元所需的最小硬币数量if(amount == 0)return 0;int[] dp = new int[amount+1];Array.Fill(dp, int.MaxValue);dp[0] = 0;for(int i = 1; i <= amount; i++){foreach(int coin in coins){//当前硬币小于当前要得到的总金额,当前这个硬币+另一个金额数if(coin <= i && dp[i-coin] != int.MaxValue)dp[i] = Math.Min(dp[i], dp[i - coin] + 1);}}return dp[amount] == int.MaxValue ? -1 : dp[amount];}
}
时间复杂度:O(N * Amount)
空间复杂度:O(Amount)
其他思路:
当然,这题也可以用深度优先搜索 + 记忆化剪枝的方式来解决
我们用一个长度为amount + 1的数组 count[]来记录每个金额需要的最少硬币数
深度优先搜索的递归函数的返回值是当前金额需要的最小凑配硬币数量
深度优先搜索的递归函数每次传入金币数组和当前还需要凑配的金额rem,它出口就有三个:
①当前需要凑配的金额为负数的时候,返回-1
②当前需要凑配的金额为0的时候,返回0
③当前需要凑配的金额已经记录在数组中,直接返回
而在具体的逻辑处理则是:
遍历硬币数组,用rem - coin进行递归搜索得到结果值
如果结果值在合理区间内(>= 0 ) 而且 比当前已经记录的最小硬币数还小,则更新最小硬币数
最后如果当前最小硬币数不为初始设定的最大值,则返回最小硬币数,否则返回-1
具体代码如下:
public class Solution {private int[] count;public int CoinChange(int[] coins, int amount) {count = new int[amount+1];return dfs(coins, amount);}public int dfs(int[] coins, int rem){if(rem < 0) return -1; //说明当前凑不成if(rem == 0) return 0; //说明已经凑成了if(count[rem] != 0) return count[rem]; //说明之前已经凑好过这一部分//配对的另一半金额需要的最少硬币数int min = int.MaxValue;foreach(int coin in coins){//根据当前硬币的面值,去寻找另一半配对的金额int res = dfs(coins, rem - coin);//如果配对金额找得到,而且配对花的硬币数是最少的,就更新为新的最少硬币if(res >= 0 && res < min){min = res + 1;}}count[rem] = min == int.MaxValue ? -1 : min;return count[rem];}
}
时间复杂度:O(N * Amount)
空间复杂度:O(Amount)
总结:
①如果是很明显当前状态的结果值可以通过拆分为更小的子状态来得到的,那就是可以用深搜+记忆化 或者 动态规划来处理的问题
②对于动态规划的问题,我们要明确好状态dp[i]以及它对应的状态转移方程
③这里的状态转移的关键的每个硬币的默认面值