LeetCode 279. Perfect Squares

164 阅读1分钟

LeetCode 279. Perfect Squares

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

 

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9

 

提示:

  • 1 <= n <= 104

算法1

(动态规划) O(nn\sqrt{n})
设 f(i) 表示通过平方数组成 i 所需要完全平方数的最少数量。
初始时,f(0)=0,其余待定。
转移时,对于一个 i,枚举 j,f(i)=min(f(i−j∗j)+1),其中 1≤j≤i\sqrt{i}
最终答案为 f(n)。

时间复杂度

实际复杂度为 S=i=1n\sum_{i=1}^n i\sqrt{i},通过积分近似上界,得到 S=O(nn\sqrt{n})。

空间复杂度

需要额外 O(n)的空间存储状态。

C++ 代码

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

        return f[n];
    }
};

算法2

(宽度优先搜索) O(nn\sqrt{n})
通过宽搜来优化动态规划的效率,可以将整个过程看做一张图,每个数字都是一个点,两个数字之间差距为平方数时有一条单向边。
使用宽搜来求从 0 到 n 的最短路。

时间复杂度

宽搜的时间复杂度为 O(n+m),这里的点数,也就是数字个数 n,边数同算法 1 中的分析是 O(i\sqrt{i})。
故总时间复杂度仍然是 O(nn\sqrt{n}),但由于宽搜可能能快速找到到结点 n 的路径,常数会比较优。

空间复杂度

需要额外 O(n) 的空间存储队列和距离数组。

C++ 代码

class Solution {
public:
    int numSquares(int n) {
        vector<int> f(n + 1, n);
        queue<int> q;
        f[0] = 0;
        q.push(0);
        while (!q.empty()) {
            int s = q.front();
            if (s == n)
                break;
            q.pop();
            for (int i = 1; s + i * i <= n; i++)
                if (f[s + i * i] > f[s] + 1) {
                    f[s + i * i] = f[s] + 1;
                    q.push(s + i * i);
                }
        }
        return f[n];
    }
};

算法3

(数学) O(n\sqrt{n}+log(n))
根据 拉格朗日四平方和定理,可以得知答案必定为 1, 2, 3, 4 中的一个。
其次根据 勒让德三平方和定理,可以得知当 n=4a4^a(8b+7)时,n 不能写成 3 个数的平方和。
然后可以根据以上定理和枚举,判断出答案是否为 1, 2, 3,若都不是则答案为 4。

时间复杂度

判断平方数的时间复杂度为 O(1),枚举答案为 2 的时间复杂度为O(n\sqrt{n}),判断答案是否为 4 的时间复杂度为 O(log(n)),故总时间复杂度为 O(n\sqrt{n}+log(n))。

C++ 代码

class Solution {
public:
    int numSquares(int n) {
        if ((int)sqrt(n) * (int)sqrt(n) == n)
            return 1;
        int t = n;
        while ((t & 3) == 0) t >>= 2;
        if (((t - 7) & 7) == 0)
            return 4;

        for (int i = 1; i * i <= n; i++)
            if ((int)(sqrt(n - i * i)) * (int)(sqrt(n - i * i)) == n - i * i)
                return 2;

        return 3;
    }
};