先决条件–动态编程,如何解决动态编程问题?
有以下两种不同的方法来存储值,以便可以重用子问题的值。在这里,将讨论解决DP问题的两种模式:
- 列表:自下而上
- 备注:自上而下
在了解以上两个术语的定义之前,请考虑以下声明:
- 版本1 :我将从GeeksforGeeks学习动态编程的理论,然后在经典DP上练习一些问题,因此我将掌握动态编程。
- 第2版:要掌握动态编程,我必须练习动态问题并练习问题–首先,我必须从GeeksforGeeks学习一些动态编程理论
上面的两个版本都说相同的东西,只是区别在于传达消息的方式,这正是Bottom Up和Top Down 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,例如“最小成本路径”,此处的内存是没有按顺序填充。