【问题标题】:Pyramids dynamic programming金字塔动态规划
【发布时间】:2015-11-14 20:43:59
【问题描述】:

我在采访中遇到了这个问题,无法弄清楚。我相信它有一个动态编程解决方案,但它让我望而却步。

给定一些砖块,输出可能的二维金字塔的总数,其中金字塔被定义为任何结构,其中一排砖块的砖块数量少于其下方的一排砖块。您不必使用所有积木。

砖只是一个正方形,一排砖的数量是唯一重要的信息。

真的坚持这个,我认为迭代解决每个问题 1...n 并求和会很容易。但是用 i 块砖计算出可能的金字塔数量让我望而却步。

例如,n = 6

X

XX

X
XX   XXX

X
XXX   XXXX

XX      X
XXX   XXXX   XXXXX

X
XX      XX       X
XXX   XXXX   XXXXX   XXXXXX

所以答案是 6 块砖有 13 个可能的金字塔。

编辑

我很肯定这是一个动态编程问题,因为(一旦您确定了第一行)只需查看您记忆的剩余砖块数组中的索引,看看有多少金字塔适合顶部是有意义的。

考虑宽度至少为 n/2 的底部行也是有意义的,因为我们顶部不能有比底部行更多的砖块除了,这就是我失去它和我的想法的地方分崩离析,在某些(少数情况下)你可以即N = 10

X
XX
XXX
XXXX

现在底行有 4 个,但顶部还剩下 6 个

但是在 n = 11 的情况下,我们不能有少于 n/2 个砖块的底行。还有另一个奇怪的不一致,比如 n = 4,我们不能有 n/2 = 2 块的底行。

【问题讨论】:

  • 如果你不必使用所有的积木——使用 0 块积木可以吗?那么 - 为什么不是 14 个合法的金字塔 n = 6?
  • 不确定,我不太记得问题陈述,但我认为最好将金字塔的定义限制在至少一块砖上。好电话我没有在问题陈述中提到这一点。
  • 听起来非常适合递归函数。对于任何组合/排列问题,我首先会考虑递归而不是动态编程。
  • 你的陈述“一旦你考虑第一行,找出你可以在上面建造多少金字塔”是一个递归陈述
  • 记忆是一种优化。当您仍处于设计算法的阶段时,请忘记它。

标签: algorithm dynamic-programming


【解决方案1】:

你可以用多少种方法构建宽度为 n 的金字塔?通过将宽度为 n-1 或更小的任何金字塔放置在 n 层砖块顶部的任何位置。所以如果p(n)是宽度n的金字塔的个数,那么p(n) = sum [m=1 to n-1] (p (m) * c(n, m)),其中c(n, m)是你可以放置宽度为m的层的方式数在宽度为 n 的层上(我相信你可以自己解决这个问题)。

不过,这并没有限制积木的数量。通常,在 DP 中,任何资源限制都必须建模为单独的维度。所以你现在的问题是 p(n, b):“你可以用总共 b 个砖块建造多少个宽度为 n 的金字塔” ?在递归公式中,对于在当前金字塔顶部构建较小金字塔的每种可能方式,您需要参考正确的剩余砖块数量。我把它作为一个挑战留给你,让你计算出递归公式;如果您需要任何提示,请告诉我。

【讨论】:

  • 我没有将宽度(底行)视为一个维度。我只是在想 n 块砖被详尽地用于创建一些金字塔。例如,如果 n = 3,对于 i = 1,我们有一个单独的砖作为金字塔,对于 i = 2,我们有一行 2,对于 i = 3,我们有 (3) 和 (2, 1)。当根据砖块的数量,宽度可能会改变时,如何使用宽度作为约束?
  • 我相当有信心使用所有 n 个积木,我们可以在底行使用 n 到 n/2 个积木之间的 k,尽管我不确定如何证明这一点。然后对于其中的每一个,我们添加一个 + 金字塔的数量,我们可以在上面使用 n - k 个砖块建造。正确的轨道?
  • @shane:我忘了提,但实际上,你需要尝试所有可能的底层宽度,看看哪一个给出的结果最高。
  • 因此,如果我将宽度排除在递归之外,那么我可以接近 p(b) = sum(0
  • @shane:你不能忽略宽度,因为你需要知道你将构建的下一个级别的宽度限制。示例:对于n=6,从底宽3开始有效,对于n=7,从底宽开始有效4,在这两种情况下,你都会问你可以用 n=3 剩余的砖块建造什么。但是,仅当您正在构建的关卡包含至少 4 个积木时,才可以选择将所有 3 块砖放在同一关卡中。
【解决方案2】:

你可以把你的递归想象成:给定x在最后一行使用n的砖块,你可以建造多少个金字塔。现在您可以从上到下或从下到上填充行。我将解释前一种情况。
这里的递归可能看起来像这样(left 是剩余的积木数量,last 是最后一行使用的积木数量)

f(left,last)=sum (1+f(left-i,i)) for i in range [last+1,left] inclusive.

因为当您在当前行使用i 积木时,您将剩下left-i 积木,i 将是该行使用的积木数。

代码:

int calc(int left, int last) {
    int total=0;
    if(left<=0) return 0;  // terminal case, no pyramid with no brick
    for(int i=last+1; i<=left; i++) {
        total+=1+calc(left-i,i);
    }
    return total;
}

我将留给您实现memoizedbottom-up dp 版本。此外,您可能希望从底行开始填充金字塔中的顶行。

【讨论】:

  • 您能解释一下您选择的范围吗?
  • 由于您在最后一行使用了最后数量的积木,因此您必须在这一行至少使用 last+1 个积木(因为我们是从上到下),并且您最多可以有这排砖,因为这是你剩下的所有砖。
  • 删除了我的评论,没有意识到我们要从上到下
【解决方案3】:

让我们选择一个合适的定义:

f(n, m) = # pyramids out of n bricks with base of size < m

您现在正在寻找的答案是(假设N 是您输入的积木数量):

f(N, N+1) - 1

让我们分解一下:

  • 第一个N 很明显:这是你的积木数量。
  • 您的底行最多包含N 砖块(因为这就是您所拥有的全部),因此N+1 是一个足够的下限。
  • 最后,- 1 在那里,因为从技术上讲,空金字塔也是一个金字塔(因此将被计算在内),但您将其排除在您的解决方案之外。

基本情况很简单:

f(n, 0) = 1   for any n >= 0
f(0, m) = 1   for any m >= 0

在这两种情况下,我们在这里计算的都是空金字塔。


现在,我们仍然需要一个用于一般情况的递归公式。

假设我们得到nm 并选择在底层放置i 砖块。我们可以在这一层之上放置什么?一个较小的金字塔,我们剩下n - i 砖块,其底部大小为&lt; i。这正是f(n - i, i)

i 的范围是多少?我们可以选择一个空行,所以i &gt;= 0。显然,i &lt;= n 因为我们只有n 砖块。而且,i &lt;= m - 1,根据 m 的定义。

这导致递归表达式:

f(n, m) = sum f(n - i, i) for 0 <= i <= min(n, m - 1)

您可以递归计算f,但使用动态编程当然会更快。存储结果矩阵很简单,所以我把它留给你。


回到原来的说法,即f(N, N+1)-1 是您正在寻找的答案,只要是&gt; N,为m 选择哪个值并不重要。根据递归公式,很容易证明f(N, N + 1) = f(N, N + k) 对应于每个k &gt;= 1

f(N, N + k) = sum f(N - i, i) for 0 <= i <= min(N, N + k - 1)
            = sum f(N - i, i) for 0 <= i <= N
            = sum f(N - i, i) for 0 <= i <= min(N, N + 1 - 1)

【讨论】:

  • f(n, n+1) 作为最终答案没有意义。我认为应该是 f(x,y),其中 y 应该总是小于 x,否则就不是金字塔了。
  • @newbie_old 你读过为什么f(N, N+1)-1 是答案的解释吗?
  • @VincentvanderWeele 我读到它说它是足够的上限,但我只是想知道 f(6,5) 对于 N=6 是否足够?
  • @newbie_old 根据f的定义,m表示底层的最大尺寸。所以f(6, 5) 只允许底部为4 和更小的金字塔,因此不会全部计算(它缺少底部为56 块的金字塔)。 f(6, 7) 就足够了,因为它允许一个底部为6 块的金字塔。我在答案中添加了一段,表明这相当于f(6, 8)f(6, 9),等等。
  • 啊,@newbie_old,直到现在我才意识到它说的是上限而不是下限。这解释了你的困惑
【解决方案4】:

由于我们被要求计算任何基数小于或等于n 的金字塔,我们可以依次考虑每个基数(1 个元素、2 个元素、3 个...等的金字塔)并将它们相加。但是我们可以用多少种不同的方式从k 元素组成一个金字塔呢?与k 的不同分区计数相同的数字(例如,对于k = 6,我们可以有(6), (1,5), (2,4), and (1,2,3))。在WikipediaOEIS 中描述了不同分区计数的生成函数/循环。

重复,基于Pentagonal number Theorem

q(k) = ak + q(k − 1) + q(k − 2) − q(k − 5) − q(k − 7) + q(k − 12) + q(k − 15) − q(k − 22)...
  where ak is (−1)^(abs(m)) if k = 3*m^2 − m for some integer m and is 0 otherwise.

(The subtracted coefficients are generalized pentagonal numbers.)

由于 Wikipedia 中描述的递归要求计算所有前面的 q(n) 以得出更大的 q(n),因此我们可以简单地将沿途的结果相加以获得我们的结果。

JavaScript 代码:

function numPyramids(n){

  var distinctPartitions = [1,1],
      pentagonals = {},
      m = _m = 1,
      pentagonal_m = 2,
      result = 1;

  while (pentagonal_m / 2 <= n){
    pentagonals[pentagonal_m] = Math.abs(_m);
    m++;
    _m = m % 2 == 0 ? -m / 2 : Math.ceil(m / 2);
    pentagonal_m = _m * (3 * _m - 1);
  }

  for (var k=2; k<=n; k++){
    distinctPartitions[k] = pentagonals[k] ? Math.pow(-1,pentagonals[k]) : 0;
    var cs = [1,1,-1,-1],
        c = 0;
    for (var i in pentagonals){
      if (i / 2 > k)
        break;
      distinctPartitions[k] += cs[c]*distinctPartitions[k - i / 2];
      c = c == 3 ? 0 : c + 1;
    }
    result += distinctPartitions[k];
  }
  return result;
}

console.log(numPyramids(6)); // 13

【讨论】:

    猜你喜欢
    • 2014-08-11
    • 2012-03-27
    • 1970-01-01
    • 2019-09-14
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    相关资源
    最近更新 更多