LeetCode 279. Perfect Squares
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9
提示:
1 <= n <= 104
算法1
(动态规划) O(n)
设 f(i) 表示通过平方数组成 i 所需要完全平方数的最少数量。
初始时,f(0)=0,其余待定。
转移时,对于一个 i,枚举 j,f(i)=min(f(i−j∗j)+1),其中 1≤j≤。
最终答案为 f(n)。
时间复杂度
实际复杂度为 S= ,通过积分近似上界,得到 S=O(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(n)
通过宽搜来优化动态规划的效率,可以将整个过程看做一张图,每个数字都是一个点,两个数字之间差距为平方数时有一条单向边。
使用宽搜来求从 0 到 n 的最短路。
时间复杂度
宽搜的时间复杂度为 O(n+m),这里的点数,也就是数字个数 n,边数同算法 1 中的分析是 O()。
故总时间复杂度仍然是 O(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(+log(n))
根据 拉格朗日四平方和定理,可以得知答案必定为 1, 2, 3, 4 中的一个。
其次根据 勒让德三平方和定理,可以得知当 n=(8b+7)时,n 不能写成 3 个数的平方和。
然后可以根据以上定理和枚举,判断出答案是否为 1, 2, 3,若都不是则答案为 4。
时间复杂度
判断平方数的时间复杂度为 O(1),枚举答案为 2 的时间复杂度为O(),判断答案是否为 4 的时间复杂度为 O(log(n)),故总时间复杂度为 O(+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;
}
};