【问题标题】:Recursion for Catalan number to Memoized加泰罗尼亚数递归到记忆
【发布时间】:2017-04-06 01:45:00
【问题描述】:

我被要求编写一个递归函数,该函数将计算沿具有 n × n 个方形单元格的网格边缘的单调格子路径的加泰罗尼亚数,这些单元格不会超过对角线 (pic)

我不允许使用 for 循环,只能使用递归调用...这就是我所做的:

public long C(int n) {
    if (n == 1)
        return 0;
    return C(n, 0, 0);
}

private long C(int n, int i, int j) {

    // CAN MOVE UP & RIGHT
    if (j - i > 0 && j + 1 <= n)
        return paths(n, i + 1, j) + paths(n, i, j + 1);
    // CAN MOVE UP
    else if (j - i > 0)
        return paths(n, i + 1, j);
    // CAN MOVE RIGHT
    else if (j + 1 <= n)
        return paths(n, i, j + 1);
    // CAN'T MOVE
    else
        return 1;
}

我不知道这段代码是否最好,但它可以工作...我想将此函数转换为 Memoized 函数。但我不明白它如何以及为什么它会使功能更高效。我理解为什么记忆中的斐波那契更有效,但是在这里我总是必须到达路径的末尾然后返回 1 或 0 那么如果我将 1 / 0 存储在一个数组中有什么关系呢?

感谢您的任何帮助

【问题讨论】:

  • 记住它很容易。这就是你所需要的吗?
  • @ShreyansSheth 嘿,是的,谢谢
  • 给我一月。应该做
  • 你去。希望这样做。仅此而已!

标签: java catalan


【解决方案1】:

但我不明白 [...] 为什么它会使函数更有效率。

看图,图片从1开始编号,(x,y)与左下角(0,0)坐标,可以看到图片2、3、4、5、6、7、8、10, 12,都经过(3,1)的点。

来自(3,1)的路径:

  • (3,1) → (4,1) ↑ (4,2) ↑ (4,3) ↑ (4,4)
    • 图片2、4、5
  • (3,1) ↑ (3,2) → (4,2) ↑ (4,3) ↑ (4,4)
    • 图3、6、8
  • (3,1) ↑ (3,2) ↑ (3,3) → (4,3) ↑ (4,4)
    • 图7、10、12

如您所见,您多次 (3) 次在同一条路径上行走。如果您可以缓存(记忆)从(3,1) 到结尾有 3 条路径这一事实,您就可以节省时间。

随着网格变大,节省的时间会越来越多。


所以,你要做的是,当你第一次到达一个点时,你使用递归计算结果,就像你已经做的那样,然后保存那个点的数字,当再次到达那个点时,你只需使用缓存值:

public static long paths(int n) {
    if (n == 1)
        return 0;
    return paths(n, 0, 0, new long[n][n]);
}
private static long paths(int n, int y, int x, long[][] cache) {
    long result = cache[y][x];
    if (result == 0) {
        if (y < x && x < n) // CAN MOVE UP & RIGHT
            result = paths(n, y + 1, x, cache) + paths(n, y, x + 1, cache);
        else if (y < x) // CAN MOVE UP
            result = paths(n, y + 1, x, cache);
        else if (x < n) // CAN MOVE RIGHT
            result = paths(n, y, x + 1, cache);
        else // CAN'T MOVE
            result = 1;
        cache[y][x] = result;
    }
    return result;
}

【讨论】:

  • 我对你的回答投了反对票,因为你没有完全回答这个问题。只给出了解释。我相信你和我的回答会是一个很好的组合。所以我把它颠倒了
  • @ShreyansSheth 添加代码
  • 非常感谢!我不明白为什么,但这也崩溃了(对于 n = 4,它在 (x
  • 好吧,我明白了,那是因为数组应该是 n+1 x n+1 。再次感谢您的帮助!
【解决方案2】:

您似乎知道什么是记忆。基本上,您所做的只是创建一个表memo,一旦您到达它就会存储一个值,这样您就不必再次在递归中计算它。类似于为什么fibonacci(5) 不需要递归来找到fibonacci(3),如果我们已经计算过,比如fibonacci(6),因为我们已经记忆了它。我希望你能明白。这是代码,以同样的精神记忆。 Andrea 的问题具有很好的视觉效果,可以理解。

long[][]memo;  //Memo table

public long C(int n)
{
    if (n == 1)
        return 0;

    memo=new int[n+1][n+1]; //Increase to n+1 and won't crash!

    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            memo[j][i]=-1;

    return C(n, 0, 0, memo);
}

private long C(int n, int i, int j, it) {

    // CAN MOVE UP & RIGHT
    if (j - i > 0 && j + 1 <= n)
    {
        if(memo[i+1][j]==-1)
            memo[i+1][j]=paths(n, i + 1, j);

        if(memo[i][j+1]==-1)
            memo[i][j+1]=paths(n, i, j + 1);

        return memo[i+1][j]+memo[i][j+1];
    }
    // CAN MOVE UP
    else if (j - i > 0)
    {
        if(memo[i+1][j]==-1)
            memo[i+1][j]=paths(n, i + 1, j);
        return memo[i+1][j];
    }
    // CAN MOVE RIGHT
    else if (j + 1 <= n)
    {
        if(memo[i][j+1]==-1)
            memo[i][j+1]=paths(n, i, j + 1);
        return memo[i][j+1];
    }
    // CAN'T MOVE
    else
        return 1;
}

【讨论】:

  • 这看起来不错,但它崩溃了(超出边界异常)......我认为它发生是因为我将索引 0,0 称为左下角,然后当我向右移动时检查@987654328 @ 就像我应该的那样,但备忘录中没有这样的索引。你知道怎么解决吗?
  • 只需将 memo[][] 表的大小调整为最大边界大小即可。您可以达到的最大长度是多少?另外,i 和 j 可以变成负数吗?我这种情况下,我们可以使用HashMap
  • 感谢 Shreyans,我现在正在尝试您和 Andrea 的代码。我不明白为什么他们都崩溃了,我真的很努力去理解,但我不明白!有什么提示吗?
  • 两者的代码是一样的。是否愿意在 ideone.com 发布您的整个代码并在此处发布链接?
  • 我已经更新了答案中的代码。这个对我有用。只是将大小增加到 n+1。 :) 这是有效的代码:ideone.com/9yiOBI
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 1970-01-01
  • 1970-01-01
  • 2012-04-19
相关资源
最近更新 更多