279. Perfect Squares

你快乐吗

Posted by bbkgl on November 25, 2019

从此无心爱良夜

任他明月下西楼

C++,dp背包,数学题。

dp(背包问题)

首先想到的就是dp,如果用数组dp来记录n由m个完全平方数组成的话,就是dp[n] = m.

对于数字n,可以进行分解,分解成某个数s和完全平方数的和,于是就有了dp[n] = dp[s] + 1

然后我们假设\(dp(n) \)表示数字\(n \)最少可以表示为\(dp(n) \)个完全平方数的和,就能写出状态转移方程:

\[dp(n) = min\{dp(n), dp(n - i * i) + 1\}\]

然后就能进入写代码的思路了。

因为上述公式中\(dp(n) \)是基于\(dp(n - i * i) \)才能得到最小值,所以我们需要在从小到大进行遍历,然后逐步拿到最优解。

代码很简短:

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

数学解法

数学解法是在评论区看到的,也就是有个理论:任何一个数,都可以由小于等于4个的完全平方数相加得到。

然后根据这个理论,有个推论:

当\(n \)满足如下公式时,才只能由4个完全平方数得到, \(a \)和\(b \)为某个整数:

\[n = 4^a (8b + 7)\]

否则,就是只能由1-3个完全平方数得到。

由一个完全平方数得到就非常好验证了。。。这个大家都懂,直接开方。。。

由两个完全平方数相加,其实也很好懂,直接从小到大循环,直接看他是不是等于两个完全平方数相加就行了。

也就是:

// 验证1
int m = static_cast<int>(sqrt(n));
// 验证2
if (m * m == n) return 1; 
for (int i = 1; i * i <= n; i++) {
    int j = static_cast<int>(pow(n - i * i, 0.5));
    if (j * j + i * i == n) return 2;
}

那由三个完全平方数相加怎么验证呢? 排除上述三种不就是了????

所以就很容易写出代码了:

class Solution {
public:
    int numSquares(int n) {
        // 验证1
        int m = static_cast<int>(sqrt(n));
        // 验证2
        if (m * m == n) return 1; 
        for (int i = 1; i * i <= n; i++) {
            int j = static_cast<int>(pow(n - i * i, 0.5));
            if (j * j + i * i == n) return 2;
        }
        // 验证4
        while (n % 4 == 0) n /= 4;
        if (n % 8 == 7) return 4;
        return 3;
    }
};