【问题标题】:Memoization of redundant paths冗余路径的记忆
【发布时间】:2018-03-21 19:59:53
【问题描述】:

我正在解决一个有 r 行和 c 列的网格的问题。我们从左上角的单元格开始,到右下角的单元格结束。限制是我们一次只能移动一个单元格,向下或向右。此外,某些单元格可能被列入黑名单。问题是找到总数。我们可以从源到目标的方式。

这是我的解决方案,它很简单,但在指数时间内运行:

int count(boolean[][] array, int r, int c)
{
    if ((r < 0 || c < 0) || !array[r][c]) return 0;
    if (r == 0 && c == 0) return 1;
    return count(array, r - 1, c) + count(array, r, c - 1);
}

我遇到的问题是在记忆这个时。

  1. 记忆化能否让这个解决方案更高效?
  2. 如果是这样,那么我无法将失败路径中的所有单元格列入黑名单,因为可能存在通过这些单元格的其他路径可能导致目标。所以我很困惑我应该在这里缓存什么以及在哪里添加额外的检查以避免检查我已经经历过的路径。
  3. 如果 (1) 是肯定的,那么如果没有单元格被列入黑名单,那么我想知道记忆是否有任何目的。

【问题讨论】:

    标签: java dynamic-programming memoization


    【解决方案1】:

    记忆化能让这个解决方案更高效吗?

    是的!

    如果是这样,那么我无法将失败路径中的所有单元格列入黑名单,因为可能存在通过这些单元格的其他路径可能导致目标。

    正确。

    所以我很困惑我应该在这里缓存什么以及在哪里添加额外的检查以避免检查我已经通过的路径。

    这就是你要做的。

    创建一个可空整数的 r x c 二维数组,我们称之为a。数组的含义是“a[x][y] 给出从 (x, y) 到 (r-1, c-1) 的路径数”——假设 (r-1, c-1) 是“退出”我们试图到达的单元格。

    数组将从每个元素开始为空。那太棒了。 Null 表示“我不知道”。

    用零填充数组中的每个“阻塞”单元格。这意味着“没有办法从这个牢房到出口”。

    如果a[r-1][c-1] 为零,则出口被阻塞,我们就完成了。每个查询的答案都是零,因为没有办法到达出口。假设出口单元没有被阻塞。

    有一种方法可以从退出单元格到达自身,因此将a[r-1][ c-1] 填入 1。

    现在算法是这样进行的:

    • 我们被要求从单元格(x, y) 开始提供解决方案。
    • 咨询阵列。如果它为空,则在右侧和向下的邻居中递归,并在[x][y] 处使用这些答案的sum 填充数组
    • 现在数组肯定填好了,所以返回a[x][y]

    让我们举个例子。假设我们有

    n  n  n
    n  n  0
    n  n  1
    

    我们被要求提供 (0, 1) 的解。我们没有解决方案。所以我们试图找到 (1, 1) 和 (0, 2) 的解。

    我们没有 (1, 1) 的解决方案。所以我们必须得到 (1, 2) 和 (2, 1) 的解。

    (1, 2) 我们得到了。是 0。

    (2, 1) 我们没有,但 (2, 2) 我们有,这是唯一的邻居。 (2, 2) 是 1,所以我们填入 (2, 1):

    n  n  n
    n  n  0
    n  1  1
    

    现在我们有足够的信息来填写 (1, 1):

    n  n  n
    n  1  0
    n  1  1
    

    我们还没有完成 (0, 2)。它有一个为零的邻居,所以是:

    n  n  0
    n  1  0
    n  1  1
    

    现在我们可以填写 (0, 1)

    n  1  0
    n  1  0
    n  1  1
    

    这是我们一直在寻找的,所以我们完成了。

    替代解决方案:预先计算数组。

    • 我们首先像以前一样在出口处填写所有零和一个。
    • 现在从下往上填写最右边的一列:全为 1,直到到达第一个零,此时它变为全零。
    • 现在从右到左填写最底部的行。同样,它都是 1,直到您到达第一个零,此时它变为全零。
    • 现在我们有足够的信息来填写右数第二列和倒数第二行;你明白吗?
    • 这样继续,直到填满整个数组。
    • 现在所有答案都在数组中。

    例子:

    第一步:

    n  n  n
    n  n  0
    n  n  1
    

    填写外行列:

    n  n  0
    n  n  0
    1  1  1
    

    填写下一行和下一列:

    n  1  0
    2  1  0
    1  1  1
    

    最后一个:

    3  1  0
    2  1  0
    1  1  1
    

    我们完成了;整个问题都解决了。

    如果没有细胞被列入黑名单,那么我想知道记忆是否有任何目的。

    如果没有单元格被列入黑名单,则数组如下所示:

    20 10  4  1
    10  6  3  1
     4  3  2  1
     1  1  1  1
    

    这是您应该以前见过的形状,并且知道如何直接计算每个元素。提示:您通常将其视为三角形,而不是正方形。

    【讨论】:

    • 在阅读答案之前,非常感谢您抽出宝贵的时间来写,我真的很感激!当我经历它并试图理解时,我需要一些时间。如果我有任何问题,请不要介意!
    • @rgamber:不客气;我注意到我是偶然而不是故意使用与您不同的方向约定;您将出口广场定性为 (0, 0),而我将其定性为 (r-1, c-1)。两者都可以,只需选择一个约定并坚持下去。
    • 实际上,在我的代码中,当我成功到达源单元格 (0, 0) 时,我正在打印出路径。因此,随着递归展开,打印的路径从 (0,0) 开始,到 (r-1,c-1) 结束。但我明白你的意思。谢谢!
    • 谢谢,它有效,正是我想要的!回想起来,我发现它就像我们为斐波那契递归所做的简单记忆,但是是二维的。而且我觉得很愚蠢,因为我没有马上想到它。
    • @rgamber:这需要练习。练习:一个“bt 字符串”是x(,后跟两个bt 字符串,然后是)。假设我们在 bt 字符串中计算x。有一个 bt 字符串与一个:x。一有二:(xx)。有两个和三个(x(xx))((xx)x)。问题 1:给定一个整数 n,有多少个 bt 字符串和那么多xs?问题2:列出它们。 (仅适用于小型n;它增长很快!)。
    猜你喜欢
    • 2012-11-02
    • 2016-07-25
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多