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 判斷加到後三個的算法的開頭,能很大的提升運算效率。
相似題目:
參考資料:
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