从 LeetCode 279 实践动态规划

来源:互联网 发布:贝恩杯 知乎 编辑:程序博客网 时间:2024/06/05 16:05

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

原题如上,乍一看觉得很简单,但是仔细一想,自己举了几个例子,比如 n = 147, 则 n = 7² + 7² + 7²,或者 n = 11² + 5² + 1 ,而不是 12²+1+1+1。所以这个问题还是需要对平方值小于 n 的每一个值 i 进行考虑,即遍历。而剩下的对于每一个 n-i² 又是一个不确定的数,于是很自然地就想到递归,对于每一个 n-i² 不就是和 n 一样的问题吗,只不过是数字不同罢了。因此将求n的 least number of perfect square numbers 问题转化为多个 n-i² 子问题。


对于将母问题转化为多个子问题的算法,目前我学到的主要有分治算法和动态规划算法。这两种算法都是由数学上的递推公式而来。那么区别在哪里呢?


一般来说,分治算法会使用递归,将母问题转化为几个(2个以上)子问题解的和,也就是说子问题的解是组成母问题解的一部分。而动态规划算法一般将递归算法写成非递归算法(将子问题的解记录在一个表中来实现),通过对子问题解的筛选,来决定母问题的解。本题显然是后者,因此采用动态规划算法更为合适。

写这篇文章之前看了一个博主关于动态规划算法的介绍,感觉非常有趣,很适合新手入门,链接如下:

http://blog.csdn.net/woshioosm/article/details/7438834


确定一个问题能否用动态规划算法,需要确定如下几个关键点:

  • 最优子结构

对于 i² 小于 n 的值,必然有一个是最优解(不排除多个最优解,但我们只需要找到一个即可)。

  • 子问题重叠

求解每一个子问题 n-i² 的 least number of perfect square numbers 和求解母问题 n 的 least number of perfect square numbers ,是一样的问题,只不过数值大小不同罢了。

  • 子问题独立

求解一个子问题 n-i² 并不会影响另一个子问题的解。

  • 边界

最后一个子问题一定是所求的数值可以由一个平方数直接表示。

  • 备忘录

大家应该都知道递归之所以慢,很大程度上是因为进行了很多不必要的重复计算。为了保证对每一个子问题只

进行一次求解,需要使用一个表来记录所有计算过的子问题的解。


以下是使用了递推的动态规划代码实现。虽然保证每个子问题只计算了一遍,但由于使用了递归等原因,因此

效率上还是有待改善。

public class Solution {    public int numSquares(int n) {        if(n<=0)            return 0;        int record[] = new int[n+1]; //⑤备忘录,保证计算过的n的结果result再次需要时可以直接使用        for(int i=0;i<n+1;i++)            record[i] = 0;        return result(n,record);    }    public int result(int n,int record[]){ //递归,从结果向起始推    非递归,从起始向结果推        int k = Squre(n);        if(n == k*k) //③边界            return 1;        int time = Integer.MAX_VALUE;        for(int i = 1;i<=k;i++){ //④子问题独立            if(record[n-i*i] == 0)                record[n-i*i] = result(n-i*i,record); //②子问题重叠,子问题和母问题是同样的问题            time = Math.min(time,record[n-i*i]+1); //①最优子问题        }        return time;    }    public int Squre(int n){        if(n<=0)            return 0;        int i = 1;        while(i*i<=n)            i++;        return i-1;    }}


上述使用递归的动态规划算法是从结果向起始推进的。有一种效率更高的方案,不使用递归,从起始向结果推进。

二者的思考方向相反,可以借鉴一下。

dp[n] indicates that the perfect squares count of the given n, and we have:

dp[0] = 0 dp[1] = dp[0]+1 = 1dp[2] = dp[1]+1 = 2dp[3] = dp[2]+1 = 3dp[4] = Min{ dp[4-1*1]+1, dp[4-2*2]+1 }       = Min{ dp[3]+1, dp[0]+1 }       = 1dp[5] = Min{ dp[5-1*1]+1, dp[5-2*2]+1 }       = Min{ dp[4]+1, dp[1]+1 }       = 2...dp[13] = Min{ dp[13-1*1]+1, dp[13-2*2]+1, dp[13-3*3]+1 }        = Min{ dp[12]+1, dp[9]+1, dp[4]+1 }        = 2...dp[n] = Min{ dp[n - i*i] + 1 },  n - i*i >=0 && i >= 1

and the sample code is like below:

public int numSquares(int n) {int[] dp = new int[n + 1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0;for(int i = 1; i <= n; ++i) {int min = Integer.MAX_VALUE;int j = 1;while(i - j*j >= 0) {min = Math.min(min, dp[i - j*j] + 1); //从起始向结果考虑,计算从dp[1]开始到dp[n]++j;}dp[i] = min;}return dp[n];}





原创粉丝点击