📜  制表与记忆

📅  最后修改于: 2021-09-17 07:12:03             🧑  作者: Mango

先决条件——动态规划,如何解决动态规划问题?
有以下两种不同的方法来存储值,以便可以重用子问题的值。这里,将讨论解决DP问题的两种模式:

  1. 制表:自下而上
  2. 记忆:自上而下

在了解上述两个术语的定义之前,请考虑以下陈述:

  • 版本1 :我会从GeeksforGeeks学习动态规划的理论,然后在经典DP上练习一些问题,从而掌握动态规划。
  • 版本2 :要掌握动态规划,我必须练习动态问题并练习问题-首先,我必须从GeeksforGeeks学习一些动态规划理论

上述两个版本都说同样的事情,区别在于传达信息的方式,而这正是自下而上和自上而下 DP 所做的。版本 1 可以与自下而上 DP 相关,版本 2 可以与自上而下 Dp 相关。

制表法——自底向上动态规划

正如名字本身所暗示的那样,从底部开始并累积到顶部的答案。让我们从状态转换的角度来讨论。

让我们将 DP 问题的状态描述为 dp[x],其中 dp[0] 作为基本状态,dp[n] 作为我们的目标状态。因此,我们需要找到目标状态的值,即 dp[n]。
如果我们从基本状态即 dp[0] 开始我们的转换,并按照我们的状态转换关系到达我们的目标状态 dp[n],我们称之为自下而上的方法,因为很明显我们从底部开始我们的转换状态并达到最理想的状态。

现在,为什么我们称它为制表法?

要知道这一点,让我们首先编写一些代码来使用自下而上的方法计算数字的阶乘。再一次,作为我们解决 DP 的一般程序,我们首先定义一个状态。在这种情况下,我们将一个状态定义为 dp[x],其中 dp[x] 是找到 x 的阶乘。

现在,很明显 dp[x+1] = dp[x] * (x+1)

// Tabulated version to find factorial x.
int dp[MAXN];

// base case
int dp[0] = 1;
for (int i = 1; i< =n; i++)
{
    dp[i] = dp[i-1] * i;
}

上面的代码显然遵循自底向上的方法,因为它从最底层的基本情况 dp[0] 开始过渡,并到达其目标状态 dp[n]。在这里,我们可能会注意到 dp 表是按顺序填充的,我们直接从表本身访问计算出的状态,因此,我们称之为制表方法。

记忆法——自顶向下动态规划

再一次,让我们用状态转换来描述它。如果我们需要找到某个状态的值,比如 dp[n],而不是从基本状态开始,即 dp[0] 我们从状态转换后可以到达目标状态 dp[n] 的状态中询问我们的答案关系,那么就是DP的自上而下的方式了。

在这里,我们从最顶层的目标状态开始我们的旅程,并通过计算可以到达目标状态的状态值来计算其答案,直到我们到达最底层的基本状态。

再次,让我们以自顶向下的方式编写阶乘问题的代码

// Memoized version to find factorial x.
// To speed up we store the values
// of calculated states

// initialized to -1
int dp[MAXN]

// return fact x!
int solve(int x)
{
    if (x==0)
        return 1;
    if (dp[x]!=-1)
        return dp[x];
    return (dp[x] = x * solve(x-1));
}

正如我们所看到的,我们将最新的缓存存储到一个限制,这样如果下次我们从同一状态收到调用,我们只需从内存中返回它。因此,这就是为什么我们将其称为记忆化,因为我们正在存储最新的状态值。

在这种情况下,内存布局是线性的,这就是为什么内存似乎像制表方法一样以顺序方式填充,但您可以考虑任何其他具有 2D 内存布局的自上而下 DP,如 Min Cost Path,这里的内存是不按顺序填写。

制表与记忆

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。