memo只在递归中有而在dp中无的原因

根本原因是解决思路是反的。以Fib为例:

  • 递归+memo:f(20) -> f(19) -> f(18) -> .... f(3) -> f(2) -> f(1):自顶向下:那算前面的最终res的时候,可不就要去查一查小值对应的f(n)之前有没有计算过;所以在状态转移之前有lookup的过程;
  • dp: f(1) -> f(2) -> f(3) -> .... f(18) -> f(19) -> f(20): 自底向上:本来就是从下面往上增加:内含用到了前面temporary计算中间值,就没有lookup过程。

注:dp虽然没lookup过程,但是需要额外数组记录中间值所以额外空间和递归+memo是一样的:但是含义完全不同,虽然说最后表现出来是一样的。

递归中memo的位置:在最前面初始化;每次递归都会经过base case

e.g. Coin change:

递归+memo

def coinChange(coins: List[int], amount: int):
    memo = dict()
    def dp(n):
        if n in memo: return memo[n]. # 查备忘录,避免重复计算
        # base case
        if n == 0: return 0
        if n < 0: return -1
        res = float('INF')
        for coin in coins:
            subproblem = dp(n - coin)
            if subproblem == -1: continue
            res = min(res, 1 + subproblem)
        
        # 记入备忘录
        memo[n] = res if res != float('INF') else -1
        return memo[n]
    
    return dp(amount)

dp:

int coinChange(vector<int>& coins, int amount) {
    // 数组大小为 amount + 1,初始值也为 amount + 1
    vector<int> dp(amount + 1, amount + 1);
    // base case
    dp[0] = 0;
    for (int i = 0; i < dp.size(); i++) {
        for (int coin : coins) {
            if (i - coin < 0) continue;  // 子问题无解,跳过
            dp[i] = min(dp[i], 1 + dp[i - coin]);
        }
    }
    return (dp[amount] == amount + 1) ? -1 : dp[amount];
}

推荐阅读更多精彩内容