322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
解:
一开始的想法是递归参数为i(当递归来到硬币组的第i个元素时)和sum(之前所选择的累加和)。当i越界时判断sum是否为target,如果是就返回0,表明不再需要硬币了,如果不是返回Infinity,表示决策失败。遍历尝试选择0~n个第i种硬币的情况,递归下去,取最小值返回。
改成动态规划之后,第三层的遍历即硬币个数的循环想不到斜率优化的方式,所以这个方案超时了。
const minCoin = (nums, target) => {
const res = getRes(0, 0)
return res === Infinity ? -1 : res
function getRes(i, sum) {
// 当把所有硬币都试过之后
if (i === nums.length) {
// 如果累加和是需要的结果,那么返回0表示成功,否则返回Infinity表示失败
return sum === target ? 0 : Infinity
}
// 累加的过程中超过目标也返回Infinity表示失败
if (sum > target) {
return Infinity
}
let res = Infinity
// 依次尝试当前硬币要选几枚
for (let idx = 0; idx * nums[i] + sum <= target; idx++) {
// 选几枚就加上多少作为硬币总量,然后选择最小的结果
res = Math.min(getRes(i + 1, sum + nums[i] * idx) + idx, res)
}
return res
}
}
const minCoin = (nums, target) => {
const dp = []
for (let i = 0; i <= nums.length; i++) {
dp[i] = []
}
dp[nums.length][target] = 0
for (let i = nums.length - 1; i >= 0; i--) {
for (let j = target; j >= 0; j--) {
dp[i][j] = Infinity
for (let k = 0; k * nums[i] <= target - j; k++) {
dp[i][j] = Math.min((dp[i + 1][k * nums[i] + j] ?? Infinity) + k, dp[i][j])
}
}
}
return dp[0][0] === Infinity ? -1 : dp[0][0]
}
重新设计递归参数,只有一个sum(之前的累加和)。递归函数返回达成sum还需要最少多少硬币。当累加和来到target时,返回0,表示不再需要硬币了。否则遍历硬币组,依次尝试递归,并且硬币数加一,返回最小结果。
const minCoin = (nums, target) => {
const res = getRes(0)
return res === Infinity ? -1 : res
function getRes(sum) {
if (sum >= target) {
return 0
}
let res = Infinity
for (let i = 0; i < nums.length; i++) {
res = Math.min(getRes(sum + nums[i]) + 1, res)
}
return res
}
}
const coinChange = function(nums, target ) {
const dp = []
dp[target] = 0
for (let i = target - 1; i >= 0; i--) {
dp[i] = Infinity
for (let j = 0; j < nums.length; j++) {
dp[i] = Math.min((dp[i + nums[j]] ?? Infinity) + 1, dp[i])
}
}
return dp[0] === Infinity ? -1 : dp[0]
};