I was trying to solve the Coin Change problem. I used two similar pieces of code, but the result is that one passed, while the other run timeout.I want to know why these two similar pieces of code has two different result, please explain the principle.
The leetcode link of this problem: https://leetcode.cn/problems/coin-change/description/
The input exceeds the time limit is :
coins = [186,419,83,408]
amount = 6249
1.Passed one:
var coinChange = function(coins, amount) {
const filter = amount + 1;
const memo = new Array(amount + 1).fill(filter);
const dp = function(coins, amount) {
if (amount === 0) {
return 0;
}
if (amount < 0) {
return -1;
}
if (memo[amount] !== filter) {
return memo[amount];
}
let res = Infinity;
for (let coin of coins) {
let subAmount = dp(coins, amount - coin);
if (subAmount === -1) {
continue;
}
res = Math.min(res, subAmount + 1);
}
memo[amount] = (res === Infinity) ? -1 : res;
return memo[amount];
}
return dp(coins, amount);
}
var coinChange = function(coins, amount) {
const filter = amount + 1;
const memo = new Array(amount + 1).fill(filter);
const dp = function(amount) {
if (amount === 0) {
return 0;
}
if (amount < 0) {
return -1;
}
if (memo[amount] !== filter) {
return memo[amount];
}
for (let coin of coins) {
let subAmount = dp(amount - coin);
if (subAmount === -1) {
continue;
}
memo[amount] = Math.min(memo[amount], subAmount + 1);
}
return memo[amount] === filter ? -1 : memo[amount]
}
return dp(amount);
}
The main problem is that you are not always doing the memoization. Both versions of the code have a special value filter
to indicate that no value has been memoized yet. But when for a certain amount there is no solution, the faulty code will not update memo[amount]
. Well, it will, but to the same value it already had: filter
. This means that you have not memoized the failure. The correct code will store -1
in that case.
So the fix of the first version is to change this:
return memo[amount] === filter ? -1 : memo[amount]
to this:
if (memo[amount] === filter) memo[amount] = -1;
return memo[amount];
With this fix it will work.
A remark: it is not good practice to have this statement:
memo[amount] = Math.min(memo[amount], subAmount + 1);
...as this is not yet guaranteed to be the correct, optimised result. The correct result is only known when you have finished the loop trying all possible coins. Only then you should store the final value in the memoization table. Luckily it has no negative impact, because all amounts that are checked in the deeper recursion tree are smaller than this amount, so memo[amount]
will not be consulted while this loop is still running. But it seems good practice to only store final (optimised) values in the memoization table.