动态规划解决方案的说明

时间:2017-06-12 20:29:32

标签: java algorithm dynamic-programming memoization

这就是问题:给定3到200之间的砖块n,返回可以建造的不同楼梯的数量。每种类型的楼梯应包括2个或更多个步骤。不允许两个步骤处于相同的高度 - 每个步骤必须低于前一个步骤。所有步骤必须至少包含一个砖块。步骤的高度被分类为构成该步骤的砖块总数。

例如,当N = 3时,你只有1种选择如何建造楼梯,第一步的高度为2,第二步的高度为1 :(#表示砖)

#
## 
21

当N = 4时,你仍然只有1个楼梯选择:

#
#
##
31

但是当N = 5时,有两种方法可以从给定的砖块构建楼梯。两个楼梯可以有高度(4,1)或(3,2),如下所示:

#
#
#
##
41

#
##
##
32

我在网上找到了解决方案,但我并不直观地理解动态编程解决方案。

public class Answer {

    static int[][] p = new int[201][201];

    public static void fillP() {
        p[1][1] = 1;
        p[2][2] = 1;

        for (int w = 3; w < 201 ; w++) {
            for (int m = 1; m <= w; m++) {
                if (w-m == 0) {

                    p[w][m] = 1 + p[w][m-1];

                } else if (w-m < m) {   

                    p[w][m] =  p[w-m][w-m] + p[w][m-1];

                } else if (w-m == m) {  
                    p[w][m] = p[m][m-1] + p[w][m-1];

                } else if (w-m >m) { 

                    p[w][m] = p[w-m][m-1] + p[w][m-1];
                }

            }
        }
    }

    public static int answer(int n) {

        fillP();
        return p[n][n] - 1;

    }

}

特别是,如何提出数组中每个连续条目之间的关系?

3 个答案:

答案 0 :(得分:2)

这是一个非常有趣的问题。首先,让我们尝试理解recurrence relation

如果我们当前构建了一个高度为h的步骤并且我们还剩下b块砖,那么我们从这里完成楼梯的方式数量等于我们所有方式的总和可以使用下一步h'b - h'砖来完成楼梯,0 < h' < h

一旦我们有了这种递归关系,我们就可以设计一个递归解决方案;然而,在它的当前状态下,解决方案以指数时间运行。所以,我们只需要“缓存”我们的结果:

import java.util.Scanner;

public class Stairs {
  static int LIMIT = 200;
  static int DIRTY = -1;
  static int[][] cache = new int[LIMIT + 2][LIMIT + 2];

  public static void clearCache() {
    for (int i = 0; i <= LIMIT + 1; i++) {
      for (int j = 0; j <= LIMIT + 1; j++) {
        // mark cache as dirty/garbage values
        cache[i][j] = DIRTY;
      }
    }
  }

  public static int numberOfStaircases(int level, int bricks, int steps) {
    // base cases
    if (bricks < 0) return 0;
    if (bricks == 0 && steps >= 2) return 1;

    // only compute answer if we haven't already
    if (cache[level][bricks] == DIRTY) {
      int ways = 0;
      for (int nextLevel = level - 1; nextLevel > 0; nextLevel--) {
        ways += numberOfStaircases(nextLevel, bricks - nextLevel, steps + 1);
      }
      cache[level][bricks] = ways;
    }

    return cache[level][bricks];
  }

  public static int answer(int n) {
    clearCache();
    return numberOfStaircases(n + 1, n, 0);
  }

  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    System.out.println(answer(n));
  }
}

从您提供的代码中,似乎作者又向前迈了一步,用纯粹的迭代版本替换了递归解决方案。这意味着作者提出了bottom-up solution rather than a top-down solution

我们也可以用数学方法解决问题:

How many distinct non-trivial integer partitions does n have?

因此对于n = 6,我们有:5 + 14 + 23 + 2 + 1。所以answer(6) = 3。有趣的是,Euler proved给定n的不同整数分区的数量始终与不一定的奇数整数分区的数量相同。

(作为旁注,我知道这个问题来自哪里。祝你好运!)

答案 1 :(得分:0)

有关此问题(全部为最宏伟的楼梯)的详细说明在页面上,并提供了几种不同的解决方案。

https://jtp.io/2016/07/26/dynamic-programming-python.html

答案 2 :(得分:0)

对于建造楼梯,我们可以将其视为一个金字塔,在每个台阶的顶部建造,当我们上升并完成我们的楼梯时,我们会保留一些砖块。

对于我们拥有的 n 块积木,我们可以从第一步的顶部的 i 块积木开始,这意味着我们还有 n-i 块积木用于当前步骤。当我们计算建造 n 块砖的多层楼梯的方法数量时,对于第一步 n-i,方法的数量是 - 用 i 块砖建造楼梯,楼梯可以是多层的,也可以是单步的。我们可以按照这个相对机制来得到从第 0 步开始用 n 块砖可能的楼梯总数。

为了避免为金字塔 i 计算相同的结果,我们可以使用内存缓存来存储 n 块砖的可能楼梯的结果,其中 k 作为最后一步(因为可能的楼梯将取决于前一步金字塔将被放置在其上以避免双步或最后一步变得比下一个小)。

package com.dp;

import java.util.HashMap;
import java.util.Map;

public class Staircases {

private static Map<String, Long> cacheNumberStaircasesForNBricks = new HashMap<String, Long>();

public static void main(String[] args) {

    int bricks = 1000;
    Long start = System.currentTimeMillis();
    long numberOfStaircases = getStaircases(bricks, Integer.MAX_VALUE, true);
    Long end = System.currentTimeMillis();
    System.out.println(numberOfStaircases);
    System.out.println("Time taken " + (end - start) + " ms");

}

/*
 * For n bricks returns number of staircases can be formed with minimum 2
 * stairs and no double steps, with k as the number of bricks in last step
 */
private static long getStaircases(int n, int k, boolean multilevelOnly) {

    /*
     * if the last step was same as n, you can't get a single step of n bricks as the next step, 
     * hence the staircase needs to be multilevel
     */
    if (n == k) {

        multilevelOnly = true;
    }
    /*
     * for n less than 3 ie 1 or 2 there is only one stair case possible if the last step is of greater number of bricks
     */
    if (n < 3) {
        if (k <= n) {
            return 0;
        }
        return 1;
    }

    /*
     * for n =3, if multilevel is allowed only, then only one combination is
     * there ie 2,1.
     */
    if (n == 3) {
        if (k < n) {
            return 0;
        }
        if (multilevelOnly) {
            return 1;
        }
    }

    /*
     * refer from the in-memory cache. Don't compute if we have computed for last step (k) and current bricks left (n) to build the rest of the staircase
     */
    String cacheKey = n + "-" + k;
    if (cacheNumberStaircasesForNBricks.get(cacheKey) != null) {

        return cacheNumberStaircasesForNBricks.get(cacheKey);
    }

    /* 
     * start with one case which involves a single step of n bricks.
     * for multilevel only or last step being smaller(invalid scenario) staircases, put the initial count as zero 
     */
    long numberOfStaircases = multilevelOnly || k < n ? 0 : 1;

    for (int i = 1; n - i > 0; i++) {
         
        // current step must be smaller than the last step 
        if (n - i < k) {

            numberOfStaircases += getStaircases(i, n - i, false);
        }
    }

    cacheNumberStaircasesForNBricks.put(cacheKey, numberOfStaircases);
    return numberOfStaircases;
  }
}