每日一题- day 1

221 阅读1分钟

1. 今日语录

万事开头难,先整个简单题。

2. 题目

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。

3. 思路


// 解法1
var coinChange_0 = function (coins, amount) {
    if (amount === 0) {
        return 0;
    }
    /**
     * 暴力美学,回溯大法,先得出结果
     * 1. 可以把整个过程想像成一棵树,coins数组的长度为n,初始节点的值为amount
     * 2. 每个节点最多有n个子节点,子节点的值为amount-coins[i]
     * 3. 当子节点小于0时,即为叶子节点,计算值为0的叶子节点的高度,取最小值。
     * */
    let min = Infinity;
    const dfs = (sum, coins, height) => {
        if (sum < 0) {
            return;
        } else if (sum == 0) {
            min = Math.min(min, height)
            return;
        } else {
            for (let coin of coins) {
                dfs(sum - coin, coins, height + 1)
            }
        }
    }
    dfs(amount, coins, 0)
    return min === Infinity ? -1 : min
};
​
/**
 * 解法2
 * 不用怀疑,上面解法必定是超时的。
 * 那么如何优化时间复杂度呢?以空间换时间,这是常用的办法。
 * 在回溯的过程中,其实是有很多子问题是被重复计算的。
 * 
 * 例如,coins = 【1,2,5】,amount = 11
 * 初始节点的值:11,高度为1的子节点的值:10,9,6
 * 这个大问题的结果 = 三个子问题的结果 + 1
 * 
 * 即 假设已知amount = 10,9,6 时的问题结果,3个结果取最小值,即为amount = 11的结果。
 */
var coinChange_1 = function (coins, amount) {
    let dp = new Array(amount + 1).fill(Infinity)
    const dfs = (sum, coins) => {
        if (sum <= 0) {
            return sum;
        }
        if (dp[sum] !== Infinity) {
            return dp[sum]
        } else {
            let min = Infinity;
            for (let coin of coins) {
                let res = dfs(sum - coin, coins)
                if (res >= 0 && res < min) {
                    min = res + 1;
                }
            }
            dp[sum] = min == Infinity ? -1 : min;
            return dp[sum];
        }
    }
    return dfs(amount, coins)
};
/**
 * 解法3
 * 回顾解法2,其实就是一棵递归树,由上到下计算,记录子问题的答案,可以叫记忆化搜索
 * 其实从叶子节点反过来去找初始节点,自下而上,就是我们常说的动态规划问题
 * 
 * 例如,coins = 【1,2,5】,amount = 11
 * dp[0]= 0, dp[1] = 1, dp[2] = 1,dp[5] = 1
 * dp[3] = 最小值(dp[2] + 1, dp[1] + 1) = 2
 * ……
 * 由此可知,转移方程 dp[j] = min( amount -  dp[j - coins[i]])+ 1
 * */var coinChange = function (coins, amount) {
    if (amount === 0) {
        return 0;
    }
    let dp = new Array(amount + 1).fill(Infinity)
    dp[0] = 0
    for (let i = 0; i < coins.length; i++) {
        for (let j = coins[i]; j <= amount; j++) {
            dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1)
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount];
};
​
const fn = (coins, amount) => {
    console.log('硬币:', coins);
    console.log('目标金额:', amount);
    // console.log('最少硬币数(暴力回溯):', coinChange_0(coins, amount));
    console.log('最少硬币数(记忆化搜索):', coinChange_1(coins, amount));
    console.log('最少硬币数(动态规划):', coinChange(coins, amount));
}
// fn([1, 2, 5], 11)
fn([186, 419, 83, 408], 6249)

4. 关键字

动态规划,记忆化搜索,自上而下,自下而上,图,树