Тайм-аут смены монет

Я пытался решить проблему обмена монет. Я использовал два похожих фрагмента кода, но в результате один прошел, а другой выполнился по таймауту. Я хочу знать, почему эти два похожих фрагмента кода дают два разных результата, объясните, пожалуйста, принцип.

Ссылка на литкод этой проблемы: https://leetcode.cn/problems/coin-change/description/

Ввод превышает лимит времени:

монеты = [186,419,83,408]

сумма = 6249

1.Прошел один:

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);
}

  1. тайм-аут одного запуска:
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);
}

🤔 А знаете ли вы, что...
Синтаксис JavaScript схож с синтаксисом языка программирования Java, но они не связаны.


1
56
1

Ответ:

Решено

Основная проблема в том, что вы не всегда занимаетесь мемоизацией. Обе версии кода имеют особое значение filter, указывающее, что значение еще не было сохранено. Но когда на определенную сумму решения нет, неисправный код не обновится memo[amount]. Ну, будет, но с тем же значением, которое оно уже имело: filter. Это означает, что вы не запомнили неудачу. В этом случае правильный код сохранит -1.

Итак, исправление первой версии состоит в том, чтобы изменить это:

    return memo[amount] === filter ? -1 : memo[amount]  

к этому:

    if (memo[amount] === filter) memo[amount] = -1;
    return memo[amount];

С этим исправлением все будет работать.

Замечание: не рекомендуется использовать такое утверждение:

memo[amount] = Math.min(memo[amount], subAmount + 1);

...поскольку это еще не гарантирует правильный оптимизированный результат. Правильный результат станет известен только тогда, когда вы закончите цикл, перепробовав все возможные монеты. Только тогда вам следует сохранить окончательное значение в таблице мемоизации. К счастью, это не оказывает негативного влияния, поскольку все суммы, проверяемые в дереве более глубокой рекурсии, меньше этой суммы, поэтому memo[amount] не будет учитываться, пока этот цикл все еще работает. Но кажется хорошей практикой хранить в таблице запоминания только окончательные (оптимизированные) значения.