Leetcode中的完美广场

时间:2016-08-19 04:25:03

标签: c++ math dynamic-programming perfect-square

我无法理解其中一个Leetcode问题。

  

给定正整数n,找到总和为n的最小正方数(例如,1,4,9,16 ......)。

     

例如,给定n = 12,返回3,因为12 = 4 + 4 + 4;给定n = 13,返回2,因为13 = 4 + 9。

解决方案:

int numSquares(int n) {
    static vector<int> dp {0};
    while (dp.size() <= n) {
        int m = dp.size(), squares = INT_MAX;
        for (int i=1; i*i<=m; ++i)
            squares = min(squares, dp[m-i*i] + 1);
        dp.push_back(squares);
    }
    return dp[n];
}

我真的不明白min(squares,dp[m-i*i]+1)发生了什么。你能解释一下吗?

THX。

3 个答案:

答案 0 :(得分:6)

您提到的解决方案是自下而上的算法版本。为了更好地理解算法,我建议查看解决方案的自上而下版本。

让我们仔细观察计算最小正方形量的递归关系,包含在数字N内。对于给定的N和任意数字x(被认为是最短数字序列的成员,其完美平方总和为N):

f(N, x) = 0                                 , if N = 0    ;
f(N, x) = min( f(N, x + 1), f(N - x^2, 1) ) , if N >= x^2 ;
f(N, x) = +infinity                         , otherwise   ;

solution(N) = f(N, 1)

现在,考虑到所考虑的重复,我们可以构建自上而下的解决方案(我将在Java中实现它):

int solve(int n) {
    return solve(n, 1);
}

int solve(int n, int curr) {
    if (n == 0) {
        return 0;
    }
    if ((curr * curr) > n) {
        return POSITIVE_INFINITY;
    }
    // if curr belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
    int inclusive = solve(n - (curr * curr), 1) + 1;
    // otherwise:
    int exclusive = solve(n, curr + 1);
    return Math.min(exclusive, inclusive);
}

给定解决方案的运行时复杂性是指数级的。

但是,我们注意到[1..n]的{​​{1}}和n值只有[1..sqrt(n)]个可能值。这意味着,函数curr的参数的不同值只有n * sqrt(n)组合。因此,我们可以创建memoization表并降低自上而下解决方案的复杂性:

solve

鉴于解决方案具有运行时复杂性int solve(int n) { // initialization of the memoization table int[][] memoized = new int[n + 1][(int) (Math.sqrt(n) + 1)]; for (int[] row : memoized) { Arrays.fill(row, NOT_INITIALIZED); } return solve(n, 1, memoized); } int solve(int n, int curr, int[][] memoized) { if (n == 0) { return 0; } if ((curr * curr) > n) { return POSITIVE_INFINITY; } if (memoized[n][curr] != NOT_INITIALIZED) { // the sub-problem has been already solved return memoized[n][curr]; } int exclusive = solve(n, curr + 1, memoized); int inclusive = solve(n - (curr * curr), 1, memoized) + 1; memoized[n][curr] = Math.min(exclusive, inclusive); return memoized[n][curr]; }

但是,可以将运行时复杂度降低到O(N * sqrt(N))

至于O(N)的递归关系仅取决于f(N, x)f(N, x + 1) - 这意味着关系可以等效转换为循环形式:

f(N - x^2, 1)

在这种情况下,我们必须仅为其f(0) = 0 f(N) = min( f(N - x^2) + 1 ) , across the all x, such that x^2 <= N 个不同的参数值记住f(N)。 因此,下面介绍了N自上而下的解决方案:

O(N)

最后,所提出的自上而下的解决方案可以轻松转换为自下而上的解决方案:

int solve_top_down_2(int n) {
    int[] memoized = new int[n + 1];
    Arrays.fill(memoized, NOT_INITIALIZED);
    return solve_top_down_2(n, memoized);
}

int solve_top_down_2(int n, int[] memoized) {
    if (n == 0) {
        return 0;
    }
    if (memoized[n] != NOT_INITIALIZED) {
        return memoized[n];
    }

    // if 1 belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
    int result = solve_top_down_2(n - (1 * 1)) + 1;

    for (int curr = 2; (curr * curr) <= n; curr++) {
        // check, whether some other number belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
        result = Math.min(result, solve_top_down_2(n - (curr * curr)) + 1);
    }

    memoized[n] = result;
    return result;
}

答案 1 :(得分:5)

我也很难过。我们以n = 13为例。

  • 首先要注意的是:1 ^ 2 = 1,2 ^ 2 = 4,3 ^ 2 = 9,4 ^ 2 = 16
    • 所以13不能由大于的东西组成 3 ^ 2。一般来说,n只能由数字1到sqrt(n)
    • 组成
    • 因此,我们留下了以下数字的平方的一些组合:1,2或3。
  • 接下来我们要做的是提出递归公式。这花了我很长时间才明白。但我们基本上希望减少使用较小的n(这就是整个递归点)。我们通过从n中减去候选完美正方形来做到这一点。例如:
    • 如果我们尝试3,则dp(13)= dp(13-3 ^ 2)+ 1 = dp(4)+1。 +1将计数递增1,这是因为我们已经从13中取出了一个完美的正方形,即3 ^ 2。每个+1都是我们起飞的完美广场。
    • 如果我们尝试2,则dp(13)= 13-2 ^ 2 = dp(9)+1
    • 如果我们尝试1,则dp(13)= 13-1 ^ 2 = dp(12)+1

所以我们留下来比较dp(4),dp(9)和dp(12)中最小的一个。因此最小。

答案 2 :(得分:0)

澄清你的困惑在于问题本身。结构dp包含最小数量的正方形,它总计到dp的索引位置。

例如,squares会在3时返回n=9,但最不可能是1,这是dp[m- i*i] + 1将返回的内容。