【问题标题】:1D Memoization in Recursive solution of Longest Increasing Subsequence最长递增子序列递归解中的一维记忆
【发布时间】:2017-07-10 02:08:49
【问题描述】:

在数组中计算 LIS(最长递增子序列)是一个非常著名的动态规划问题。然而在每个教程中,他们首先展示了不使用 DP 概念的递归解决方案,然后通过应用 Bottom-Up DP(迭代解决方案)来解决它。

我的问题是:

我们将如何在 Recursive Solution 中使用 Memoization。 不仅仅是记忆,而是使用一维数组的记忆。

我做了一些研究,但找不到任何相关的东西。虽然有 2 个地方要求递归记忆化 12 但那里的解决方案是使用 2D Map / Array 进行记忆化。

无论如何用一维数组记忆解决方案,会给出错误输出。 这是我所做的:

int lis(int idx, int prev)
{
    if(idx >= N)
        return 0;

    if(dp[idx])
        return dp[idx];

    int best_ans = lis(idx+1, prev);

    int cur_ans = 0;
    if(arr[idx] > prev)
    {
        cur_ans = 1 + lis(idx+1, arr[idx]);
    }
    int ans = max(best_ans, cur_ans);
    dp[idx] = ans;
    return ans;
}

int main()
{
    // Scan N 
    // Scan arr

    ans = lis(0, -1));
    print ans;
}

虽然我知道这个解决方案给出错误输出的原因:

基于前一个值的给定索引可以有多个解决方案。

但我仍然想知道如何使用一维数组来完成。

我很想知道解决方案,因为我读过每个 DP Top-Down 解决方案都可以重新构建为 Bottom-Up,反之亦然。

如果有人能提供一些见解,那将非常有帮助。

提前致谢。

【问题讨论】:

  • 有人能解释一下我对同样问题感到困惑的这个问题吗?

标签: algorithm recursion dynamic-programming memoization lis


【解决方案1】:

这是做不到的,因为问题根本上需要二维数据结构来解决。

自下而上的方法可以通过在数据结构中一次生成一行来作弊。随着时间的推移,它会生成一个二维数据结构,但在任何给定时间,您只能看到它的一个维度。

自上而下的方法必须构建整个二维数据结构。

这是 DP 的基本权衡。写下自上而下的方法通常更容易。但自下而上的方法在任何时候都只需要拥有整体数据结构的一部分,因此内存需求显着降低。

【讨论】:

  • 感谢您的回答。是的,你是对的。我特别看到了自上而下的记忆空间需求比自下而上更多的情况。尽管在大多数情况下,这样做的原因很明显,因为不再需要上述行,但仍有一些情况不直观。您能否进一步解释相同的见解是什么?此外,我想知道为什么不能使用自上而下的方法来获得这个效用。还请解释一下 LIS 问题。
  • @ShivangBansal 原因是递归+记忆不知道何时不再需要特定的数据,因此必须保留所有数据。在自下而上,您可以知道何时真正完成了一段数据,因为您已经继续浏览数据。如果这没有直观的意义,我可以写一篇文章,但在你亲眼所见之前它不会有帮助。
  • @btilly 子问题是否有任何提示表明使用 2d 而不是 1d?对于像我这样刚刚进入动态编程的人,我可以看到这个问题的子问题,并且直观地知道,如果我可以缓存它们,我就不必处理它们,只需重新使用它们,为什么不对这个特定问题使用一维工作不起作用?请提供任何额外的解释。
  • @Spindoctor 我没有比这更好的解释了,“试着写一个递归函数,看看需要多少参数才能使函数正常工作。”实际上做几个问题会比一本解释书更好。
【解决方案2】:
def LongestIncreasingSubsequenceMemo(nums, i, cache):
    if cache[i] > 0:
        return cache[i]
    result = 1
    for j in range(i):
        if nums[i] > nums[j]:
            result = max(result, 1 + LongestIncreasingSubsequenceMemo(nums, j, cache))
    cache[i] = result
    return result

def main():
    nums = [1,2,3,4,5]
    if not nums:
        return 0        
    n = len(nums)
    cache = [0 for i in range(n)]
    result = 1
    for i in range(n):
        result = max(result, LongestIncreasingSubsequenceMemo(nums, i, cache))
    return result

if __name__ == "__main__":
    print(main())

在上述解决方案中,我们采用一维数组并为数组中的每个元素更新它。

【讨论】:

    【解决方案3】:

    这可以做到,并且不需要二维数组。因为我们需要找到在每个索引处结束的最大 LIS 所以如果我们为 arr[0] 处的元素计算 LIS,而不是一次又一次地计算,我们计算一次并将其存储在 DP[1] 中。

    如果我们为 {arr[0],arr[1]} 计算 LIS,那么我们将结果存储在 DP[2] 中,依此类推,直到 DP[n]。请参阅下面的代码以完全理解这一点。

    Memoization of the recursive code

    above code got accepted on gfg as well

    【讨论】:

    • 请在您的回答中提供更多详细信息。正如目前所写的那样,很难理解您的解决方案。
    • 欢迎来到 Stack Overflow。 Please don't post screenshots of text。它们无法被屏幕阅读器等自适应技术的用户搜索或复制,甚至无法使用。相反,将代码作为文本直接粘贴到您的问题中。如果选择它并单击{} 按钮或Ctrl+K,则代码块将缩进四个空格,这将导致其呈现为代码。
    猜你喜欢
    • 2012-11-26
    • 1970-01-01
    • 2020-06-28
    • 2012-09-19
    • 2016-05-29
    • 2016-05-04
    • 2016-02-20
    • 2021-12-04
    • 2013-07-03
    相关资源
    最近更新 更多