[Leetcode]279.完全平方數

AdamWong發表於2018-12-31

最開始的時候,我想到的動態方程很簡單,就是

dp[i]=min(dp[i],dp[i-平方數]+1)

其中i-平方數一定要大於0要不然就會越界。這個思路很簡單,舉個例子:

dp[5]=min(dp[5-1^2]+1,dp[5-2^2]+1,dp[5])

這表示5可以如下組合:

dp[5] = min(所有組成4的完全平方數+完全平方數1即dp[4]+1, 所有組成1的平方數+完全平方數4即dp[1]+1)

這個方程在初始化dp[0] ,dp[1],dp[2] 和其他的dp為一個大值之後就非常簡單了,只需往MAXN迭代就行了,演算法的效率為O(nlog n)

原始解法:

class Solution {
public:
    int numSquares(int n) {
        const int MAXN=10000;
        int dp[MAXN+1];
        memset(dp,0x3f3f3f3f,sizeof(dp));
        dp[0]=0;
        dp[1]=1;
        /*1*1=1*/
        dp[2]=2;
        /*1*1+1*1=2*/
        dp[3]=3;
        /*1*1+1*1+1*1=3*/
        dp[4]=1;
        /*2*2=4*/
        for(int i=5;i<=MAXN;i++){
            for(int j=1;i-j*j>=0;j+=1){
                dp[i]=min(dp[i],dp[i-j*j]+1);
            }
        }
        return dp[n];
    }
};

這個演算法雖然非常容易實現但效率非常的低,我們依然嘗試這個方程,換一個方向去實現。

這次使用了vector來儲存dp陣列,目的是為了節省空間。在第二層遍歷的時候

for (int j=1; i+j*j <= n; j++)

我們通過正向進行遍歷演算法方便理解。

程式碼在執行上比原來的程式碼要快,但是還是沒有改變演算法的O(nlog n)的效率

class Solution {
public:
    const int MAXN=0x3f3f3f3f;
    int numSquares(int n) {
        vector<int> dp(n+1, MAXN);
        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();
    }
};

我們看一下最高效的解法,這種解法沒有用到動態規劃而是用數學的方法解決的。我們列舉答案後發現每一個正數必由1-4個完全平方數,沒有超過4。這在數學上又叫做[四平方定理][https://baike.baidu.com/item/%E5%9B%9B%E5%B9%B3%E6%96%B9%E5%92%8C%E5%AE%9A%E7%90%86]

即滿足四數平方和定理的數n(一定由四個數構成),必定滿足n=4^a(8b+7)

這樣我們可以通過定理快速寫出如下程式碼。

class Solution {
public:
int numSquares(int n) {
        // 去除4因子
        while (n % 4 == 0)
            n /= 4;
        // 若除8餘7,滿足四平方定理,必由4個完全平方陣列成
        if (n % 8 == 7)
            return 4;
        // 再嘗試將其拆成兩個或一個完全平方數
        for (int i=0; i*i <= n; i++) {
            int b = sqrt(n - i*i);
            if (i*i + b*b == n)
                return !!i + !!b;
        }
        return 3;
  }
};

相關文章