1.说点啥
总是算法,算法,算法,林林总总少说也有小两百道了,各种文章和书籍也看了一些。
看到钱币找零,还是一筹莫展。
得改变一下自己的学习思路和状态.
- 每刷一道题,要写出各种解法和时间空间复杂度。
- 一定要写总结,这个很重要!
2. 题目描述
这个问题在我们的日常生活中更加普遍。假设我们有 1 元、2 元、5 元、10 元、20 元、50 元、100 元这些面额的纸币,它们的张数分别是 c1、c2、c5、c10、c20、c50、c100。我们现在要用这些钱来支付 K 元,最少要用多少张纸币呢?
3. 总结
3.1 解题思路
- 贪心: 从面值最大的找,不够了依次往下。 局部解不是最优解,所以要枚举所有值才是真正的答案(也就是回溯法,穷举所有答案)
- 动态规划: 一张纸币最多达到多少金额,两张纸币最多达到做少金额,递归下去。。
3.2 总结
- 贪心不行。
- 回溯可以。
- 动态规划有一维数组和二维数组两种解法,一维数组有两种解法。
- 递归有递归和递归回忆录两种解法。
- 回溯和递归是一种解法,只不过一个是思想,一个是实现。
- 一共有六种解法,七种实现。
- 第七种实现,最优。
4. 解法
4.0 贪心算法
时间:指数级别。
空间:O(n)(递归的n次空间)
let coinChange = function (coins, amount, idxCoin = 0) {
coins = coins.sort((a, b) => b - a) // 排序
return _c(coins, amount, idxCoin)
}
let _c = function (arr, amount, count = 0) {
if (amount < 0) {
return -1;
}
if (amount === 0) {
return count;
}
for (var i = 0; i < arr.length; i++) {
let max = Math.floor(amount / arr[i]);
for (var j = max; j > 0; j--) {
let res = _c(arr, amount - j * arr[i], j + count)
if (res !== -1) {
return res
}
}
}
return -1;
}
4.1 回溯(暴力穷举)(对4.2递归进行了剪枝)
时间:指数级别。
空间:O(n)(递归的n次空间)
let coinChange = function (coins, amount,idxCoin=0) {
return _c(coins, amount, idxCoin)
}
let _c = function(coins, amount, idxCoin){
if (amount === 0) {
return 0;
}
if (idxCoin < coins.length && amount > 0) {
let maxVal = Math.round(amount / coins[idxCoin]);
let minCost = Number.MAX_SAFE_INTEGER;
for (let x = 0; x <= maxVal; x++) {
if (amount >= x * coins[idxCoin]) {
let res = _c(coins, amount - x * coins[idxCoin],idxCoin + 1);
if (res !== -1) {
minCost = Math.min(minCost, res + x)
}
}
}
return (minCost === Number.MAX_SAFE_INTEGER) ? -1 : minCost
}
return -1;
}
4.2 递归(动态规划-树形式)
时间复杂度: 指数级别(不仅仅和s有关,还和s-1,s-2的指数有关)
空间复杂度: O(s)(递归的空间内存)
let coinChange = (coins, amount) => {
if (amount < 1) {
return 0;
}
return _coin(coins, amount)
}
let _coin = (coins, amount) => {
console.log(amount)
if (amount < 0) {
return -1;
}
if (amount === 0) {
return 0
}
let min = Number.MAX_SAFE_INTEGER;
for (let coin of coins) {
let res = _coin(coins, amount - coin);
if (res >= 0 && res < min) {
min = res + 1;
}
}
return min === Number.MAX_SAFE_INTEGER ? -1 : min;
}
4.3 递归+回忆录(递归优化)
时间复杂度:O(sn)
空间复杂度:O(S)
let coinChange = (coins, amount) => {
if (amount < 1) {
return 0
}
return _coin(coins, amount, new Array(amount).fill(0))
}
let _coin = (coins, amount, count) => {
if (amount < 0) {
return -1;
}
if (amount === 0) {
return 0;
}
if (count[amount - 1] !== 0) {
return count[amount - 1]
}
let min = Number.MAX_SAFE_INTEGER;
for (let coin of coins) {
let res = _coin(coins, amount - coin, count);
if (res >= 0 && res < min) {
min = res + 1;
}
}
count[amount - 1] = (min === Number.MAX_SAFE_INTEGER) ? -1 : min;
return count[amount - 1]
}
4.4 动态规划+二维数组
这是最基础的动态规划思想算法。
时间复杂度O(ssn)
空间复杂度:O(sn)
var coinChange = function(coins, amount) {
if(amount === 0){
return 0;
}
let n = amount+1;
let dp = new Array(amount+1).fill(new Array(amount+1).fill(0));
for(let i = 0; i < coins.length; i++){
dp[0][coins[i]] = 1;
}
if(dp[0][n-1]){
return 1;
}
for(let i = 1; i < n; i++){
for(let j = n-1; j>=1; j--){
dp[i][j] = dp[i-1][j];
for(let m = 0; m < coins.length; m++){
if(j + coins[m] <= amount && dp[i][j]){
dp[i][j+coins[m]] = 1;
}
}
}
if(dp[i][n-1]){
return i+1;
}
}
return -1;
};
4.5 动态规划+一维数组
时间复杂度:O(sn)
空间复杂度:O(s)
思路: dp[i] = min(dp[i-x],dp[i-y]) + 1;
var coinChange = function (coins, amount) {
let dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (let coin of coins) {
if (i - coin >= 0) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}
4.6 动态规划+一维数组(优化)
时间复杂度:O(sn)
空间复杂度:O(s)
const coinChange = (coins, amount) => {
let dp = new Array(amount + 1).fill(Number.MAX_SAFE_INTEGER);
dp[0] = 0;
for (let coin of coins) {
for (let i = coin; i <= amount; i++) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1)
}
}
return dp[amount] === Number.MAX_SAFE_INTEGER ? -1 : dp[amount]
}