[LeetCode] 279. Perfect Squares 彻底平方数 LeetCode All in One 题目讲解汇总(持续更新中...)

 

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.html

Example 1:web

Input: n = 
Output: 3 
Explanation: 1212 = 4 + 4 + 4.

Example 2:算法

Input: n = 
Output: 2
Explanation: 1313 = 4 + 9.

Credits:
Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.数组

 

又是超哥一我的辛苦的更新题目,一我的托起 LeetCode 免费题的一片天空啊,赞一个~ 这道题说是给咱们一个正整数,求它最少能由几个彻底平方数组成。这道题是考察四平方和定理,to be honest, 这是我第一次据说这个定理,天啦撸,个人数学是语文老师教的么?! 闲话很少扯,回来作题。先来看第一种很高效的方法,根据四平方和定理,任意一个正整数都可表示为4个整数的平方和,实际上是能够表示为4个之内的平方数之和,那么就是说返回结果只有 1,2,3 或4其中的一个,首先咱们将数字化简一下,因为一个数若是含有因子4,那么咱们能够把4都去掉,并不影响结果,好比2和8,3和12等等,返回的结果都相同,读者可自行举更多的栗子。还有一个能够化简的地方就是,若是一个数除以8余7的话,那么确定是由4个彻底平方数组成,这里就不证实了,由于我也不会证实,读者可自行举例验证。那么作完两步后,一个很大的数有可能就会变得很小了,大大减小了运算时间,下面咱们就来尝试的将其拆为两个平方数之和,若是拆成功了那么就会返回1或2,由于其中一个平方数可能为0. (注:因为输入的n是正整数,因此不存在两个平方数均为0的状况)。注意下面的 !!a + !!b 这个表达式,可能不少人不太理解这个的意思,其实很简单,感叹号!表示逻辑取反,那么一个正整数逻辑取反为0,再取反为1,因此用两个感叹号!!的做用就是看a和b是否为正整数,都为正整数的话返回2,只有一个是正整数的话返回1,参见代码以下:函数

 

解法一:post

class Solution {
public:
    int numSquares(int n) {
        while (n % 4 == 0) n /= 4;
        if (n % 8 == 7) return 4;
        for (int a = 0; a * a <= n; ++a) {
            int b = sqrt(n - a * a);
            if (a * a + b * b == n) {
                return !!a + !!b;
            }
        }
        return 3;
    }
};

 

这道题远不止这一种解法,咱们还能够用动态规划 Dynamic Programming 来作,咱们创建一个长度为 n+1 的一维dp数组,将第一个值初始化为0,其他值都初始化为 INT_MAX, i从0循环到n,j从1循环到 i+j*j <= n 的位置,而后每次更新 dp[i+j*j] 的值,动态更新 dp 数组,其中 dp[i] 表示正整数i能少能由多个彻底平方数组成,那么咱们求n,就是返回 dp[n] 便可,也就是 dp 数组的最后一个数字。须要注意的是这里的写法,i必须从0开始,j必须从1开始,由于咱们的初衷是想用 dp[i] 来更新 dp[i + j * j],若是 i=0, j=1 了,那么 dp[i] 和 dp[i + j * j] 就相等了,怎么能用自己 dp 值加1来更新自身呢,参见代码以下:优化

 

解法二:this

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i <= n; ++i) {
            for (int j = 1; i + j * j <= n; ++j) {
                dp[i + j * j] = min(dp[i + j * j], dp[i] + 1);
            }
        }
        return dp.back();
    }
};

 

下面再来看一种 DP 解法,这种解法跟上面有些不一样,上面那种解法是初始化了整个长度为 n+1 的 dp 数字,可是初始化的顺序不定的,而这个种方法只初始化了第一个值为0,那么在循环里计算,每次增长一个 dp 数组的长度,里面那个 for 循环一次循环结束就算好下一个数由几个彻底平方数组成,直到增长到第 n+1 个,返回便可,想更直观的看这两种DP方法的区别,建议每次循环后都打印出 dp 数字的值来观察其更新的顺序,参见代码以下:url

 

解法三:spa

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(1, 0);
        while (dp.size() <= n) {
            int m = dp.size(), val = INT_MAX;
            for (int i = 1; i * i <= m; ++i) {
                val = min(val, dp[m - i * i] + 1);
            }
            dp.push_back(val);
        }
        return dp.back();
    }
};

 

最后咱们来介绍一种递归 Recursion 的解法,这种方法的好处是写法简洁,可是运算效率不敢恭维。咱们的目的是遍历全部比n小的彻底平方数,而后对n与彻底平方数的差值递归调用函数,目的是不断更新最终结果,知道找到最小的那个,参见代码以下:

 

解法四:

class Solution {
public:
    int numSquares(int n) {
        int res = n, num = 2;
        while (num * num <= n) {
            int a = n / (num * num), b = n % (num * num);
            res = min(res, a + numSquares(b));
            ++num;
        }
        return res;
    }
};

 

讨论:解法二三四的运算效率真的不高,强推解法一,高效又易懂,若是想强行优化后三个算法,能够将解法一的前两个 if 判断加到后三个的算法的开头,能很大的提升运算效率。

 

相似题目:

Count Primes

Ugly Number II

 

参考资料:

https://leetcode.com/problems/perfect-squares/

http://bookshadow.com/weblog/2015/09/09/leetcode-perfect-squares/

https://leetcode.com/problems/perfect-squares/discuss/71505/Simple-Java-DP-Solution

https://leetcode.com/problems/perfect-squares/discuss/71512/Static-DP-C%2B%2B-12-ms-Python-172-ms-Ruby-384-ms

https://leetcode.com/problems/perfect-squares/discuss/71488/Summary-of-4-different-solutions-(BFS-DP-static-DP-and-mathematics)

 

LeetCode All in One 题目讲解汇总(持续更新中...)

相关文章
相关标签/搜索