什么可以是单排摩天大楼拼图的节省空间的算法

时间:2014-07-21 10:54:14

标签: algorithm language-agnostic dynamic-programming

我正在尝试解决skyscraper puzzle的单行变体问题。问题陈述是:

考虑一个大小为nxn的摩天大楼拼图的单行。如果我们知道从左侧和右侧可以看到多少栋建筑物,那么有多少种不同的方式可以填充高度为1..n的建筑物? (1 <<; = N&LT = 5000)

我用自下而上的动态编程来解决它。递归关系如下:

f(n,left,right)=(n-2)*f(n-1,left,right) + f(n-1,left-1,right) +f(n-1,left,right-1)
f[1][1][1]=1;

不要担心答案中的大数字,因为我应该显示模数结果。我能够从中得到正确答案,但此算法的内存要求非常高。空间复杂度为O(n ^ 3),这对于给定的问题来说太多了,因为n可以高达5,000。

你能告诉我一些替代算法吗?

编辑:

解释我的复发关系: 考虑所有与(n-1)高度的组合,现在为所有建筑增加1个高度。这将使我们的建筑物从2,3到n。现在只需要调整高度为1的建筑物。它可以在任何一侧调整,这是第二和第三项int添加。 如果它不插入侧面,那么另外用第一项表示。如果目前还不清楚,请告诉我,我真的很感激任何帮助

2 个答案:

答案 0 :(得分:1)

我将尝试解释伪代码的潜在解决方案,希望我不要混淆一些索引;如果解决方案有效,则空间复杂度可以降低到O(n*n)。假设上面给出的递归关系

f[1][1][1]=1;
f(n,left,right)
  =(n-2)*f(n-1,left,right)+f(n-1,left-1,right)+f(n-1,left,right-1)

评估为

let f:int[N][N][N];
f(1,1,1) = 1;
for ( int n = 0; i < N; n++ )
  for ( int left = 0; left < N; left++ )
    for ( int right = 0; right < N; right++ )
      f(n,left,right)
        = (n-2)*f(n-1,left,right)+f(n-1,left-1,right) +f(n-1,left,right-1);

只需使用两个&#34;切片就可以改变评估。对于最外层维度如下。

let slice1:int[N][N];
let slice2:int[N][N];
slice1(1,1) = 1;
for ( int n = 0; i < N; n++ )
  for ( int left = 0; left < N; left++ )
    for ( int right = 0; right < N; right++ )
      slice2(left,right)
        = (n-2)*slice1(left,right)+slice1(left-1,right)+slice2(left,right-1);
      exchange slice1 and slice2

希望这个想法在这个符号中变得清晰。基本思想是要注意,为了生成第n个切片,只需要第n-1个切片。这个想法类似于Pascal's triangle也可以在线性空间中进行评估的事实,至于第n行的生成,只有第n-1行是必要的。 ;总而言之,对于这两个问题,通过不存储所有中间结果,可以在空间复杂性方面改进天真的评估。

答案 1 :(得分:1)

好的,要解决这个问题,我们需要做几点观察:

  • 首先,最高的建筑将左右分开视图,因此,如果我们可以决定最高建筑的位置,我们可以分开解决左右视图问题。

  • 假设我们有一个函数f(x, n),它可以计算出我们可以从x建筑物n建筑物中看到f(left - 1, a - 1)*f(right - 1, n - a - 1)*c(a - 1, n - 1)建筑物的方式。因此,在我们确定最高建筑物的位置后,结果将是aa - 1是最高建筑物的位置,c(a - 1,n - 1)将是左侧建筑物的数量最高建筑物,a - 1函数计算从n - 1建筑物中挑选a - 1建筑物的方式数量(因为我们需要随机选择dp[n][n]建筑物放在左边侧)。

因此,我们可以创建一个f(x, n)表来存储函数a的结果。对于最高建筑的每个位置answer += dp[left - 1][a - 1]*d[right - 1][n - a - 1]*c[a - 1][n - 1]

我们有dp[n][n]

要计算O(n ^ 2)时间复杂度中的表dp[x][y],我们需要做一些其他的观察:

  • 对于每个dp[x][y] = sum of dp[x - 1][y - z - 1]*P(z, y - 1),我们有z,这意味着我们选择P建筑物并在最高建筑物之后随机隐藏它们(最高建筑物总是计算在那些看到过的建筑物中)。函数P(a,b) = b! / (b - a)!用于计算排列。
  • 作为total,我们可以看到,如果我们使用额外的数组total[x] += dp[x][y]/ y!,存储 dp[x][y] = total[x - 1]* (y - 1)! = (dp[x - 1][0]/0! + dp[x -1][1]/1! + ... + dp[x - 1][y - 1]/ (y -1)!)*(y - 1)! = dp[x - 1][0]*P(y - 1, y - 1) + dp[x - 1][1]*P(y - 2, y - 1) + ... ,我们就会:

    dp

    因此,我们可以在O(n ^ 2)中计算 for(int y = 1; y <= n; y++){ for(int x = 1; x <= y; x++){ dp[x][y] = total[x - 1]*(y - 1)!; total[x] += dp[x][y]/y!; } }

    {{1}}

有了这个,我们就完成了我们的问题,将时间和空间复杂度保持在 O(n ^ 2)

注意:经过problem statement后,我们需要计算mod 1000,000,007的路数,所以你应该使用模数倒数来解决这个问题,这也有助于避免精度问题。