LeetCode-279.Perfect Squares

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.

算法思想

  1. 动态规划。当然我们会首先尝试贪心,例如12,贪心的结果是12=9+1+1+1,可见贪心不能得到最优解,所以只能上动规了。关于动规,思想和编码都不难,不过我想结合编码讨论一下设计模式的东西,也就是在面向对象编程中如何设计一个函数,使函数调用更加高效,其中也涉及一些内存使用优化的问题,开另一篇文章讨论。
  2. 数学方法。一个会数学的程序猿可以变身金刚…数学对于效率的提升不能更酷,运行时间先卖个关子,最后再给出。
  3. 广度优先搜索。最少个数,对应空间搜索树的最小深度,很多类似的最小问题都可以用广度优先搜索解决,不过这题BFS并不适合,效率不高,代码较长,且有很多细节容易出错,不建议使用。

编码与解释

1. 动态规划:见另一篇文章。
2. 数学方法。

四平方定理:每个正整数均可表示为4个整数的平方和。
更多:https://zh.wikipedia.org/wiki/四平方和定理
三平方定理:每个自然数均可表示为3个整数的平方和,当且仅当该数不能表示为n=pow(4,a)*(8b+7),a,b是整数。
更多https://en.wikipedia.org/wiki/Legendre%27s_three-square_theorem

有了两个定理后,作为一个菜鸡程序员,应该也能搞定代码。注意两个小细节:
1.使用&,==等不明确优先级的运算符时,最好加上括号!
2.不要忘记单独对结果为2时的判断,见代码注释处//check

//cpp
class Solution {
public:
    int numSquares(int n) {
        if(n<=0) return 0;
        if(isSqure(n)) return 1;
        while((n&3)==0) n>>=2;
        if((n&7)==7) return 4;
        int sqrt_n=(int)sqrt(n);
        for(int i=1;i<=sqrt_n;++i){//check
            if(isSqure(n-i*i)) return 2;
        } 
        return 3;
    }
    bool isSqure(int n)
    {
        int sqrt_n=(int)sqrt(n);
        return sqrt_n*sqrt_n==n?1:0;
    }
};
3.广度优先搜索

搜索树大致形状:

             0 
  1       4      9     16
2 5...   5...  10..  17...

无论是广度优先还是深度优先(回溯常用),关键都是要画出空间搜索树。三个细节要注意:
注释1:提高效率,跳过同层已计算过的节点。
注释2:为什么不能使用else?结合空间搜索树考虑,else的情况包括cur+i<n&&num[cur+i-1]!=0,什么意思呢,表示该节点对应的该值在之前已经考虑过,但是能直接从这里break吗?不能!因为该值只是被计算过,不代表该节点右边的兄弟节点应该被跳过。这些兄弟节点仍然应该被计算。
注释3:队列q.pop()的在循环中的正确位置

//cpp
class Solution {
public:
    int numSquares(int n) {
        if(n<=0) return 0;
        vector<int> num(n,0);
        vector<int> ps;
        for(int i=1;i<=n/i;++i)
        {
            ps.push_back(i*i);
            num[i*i-1]=1;
        }
        if(ps.back()==n) return 1;
        queue<int> q;
        for(int &i:ps) q.push(i);
        int numSq=1;
    while(!q.empty())
    {
        ++numSq;
        int size=q.size();
            while(size--)
            {
                int cur=q.front();
                for(int &i:ps)
                {
                    if(cur+i==n) return numSq;
                    else if(cur+i<n&&num[cur+i-1]==0)//1
                    {
                        q.push(cur+i);
                        num[cur+i-1]=numSq;
                    }
                    else if(cur+i>n) break;//2
                }
                q.pop();//3
            }
        }
        return numSq;
    }
};

运行时间对比(600 test cases):

动态规划(优化版) 数学 广度优先
16ms 3ms 68ms