有许多问题可以使用动态规划解决,比如最长递增子序列问题。这个问题可以通过以下两种方法之一解决:
- 备忘录法(自顶向下)- 使用递归来解决子问题,并将结果存储在某个哈希表中。
- 表格法(自底向上)- 使用迭代方法解决问题,先解决较小的子问题,然后在执行更大的问题时使用它们。
我的问题是哪种方法在时间和空间复杂度方面更好?
有许多问题可以使用动态规划解决,比如最长递增子序列问题。这个问题可以通过以下两种方法之一解决:
我的问题是哪种方法在时间和空间复杂度方面更好?
简短回答:这取决于问题!
记忆化通常需要更多的代码,并且不太直观,但对于某些问题具有计算优势,主要是在您不需要计算整个矩阵的所有值以达到答案的情况下。
表格法更加直观易懂,但可能会计算不必要的值。如果您确实需要计算所有值,则由于开销较小,该方法通常更快。
渐进地,如果使用相同的递归关系,自顶向下的动态规划实现与自底向上的实现是相同的。然而,由于记忆化中使用了递归的开销,自底向上的实现通常更有效率。
言之凿凿的答案是:它们在渐进意义上是相同的。
更为细腻的答案是,自顶向下的解决方案通常更符合直觉,并且更能代表动态规划问题中固有的递归关系,它会在深入到基本情况之前懒惰地评估和枚举更大的子问题。只有在你确信每个子问题都将被使用和整合(例如零钱兑换)的情况下,你才会从自底向上而不是自顶向下受益。
在DS&A课程和LeetCode编辑中,因为可怕的“r-word[1]”,人们往往立即排斥自顶向下的解决方案。然而,如果正确实现,自顶向下的动态规划可以与编译器合作,并产生良好执行的优化效果,甚至能够匹敌或超越自下而上、计算制表法。像Combination Sum (I、II、III、IV)系列这样充分利用“惰性求值”的非结构化、即兴问题,以及Advent of Code 2022的第16和第19天的分支限界问题,自顶向下的思维模式都能从中受益。而自下而上的解决方案只会增加家中的取暖器数量。
我通常给人们的一个更互动的例子是用来表示受限分割问题的划分数三角形。通过研究页面上指出的递归关系(p(n, k) = p(n - 1, k = 1) + p(n - k, k)
),你可以自下而上地计算整个三角形,或者你可以即时地(自上而下)计算数值,并手动跟踪几组输入,立即就能看出其中一种方法相对较差。
[1] 递归,你懂的。