[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.

Example 1:

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

Example 2:

Input: n = 13
Output: 2
Explanation: 13 = 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,參見代碼如下:

 

解法一:

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來更新自身呢,參見代碼如下:

 

解法二:

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 數字的值來觀察其更新的順序,參見代碼如下:

 

解法三:

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 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM