Leetcode(633)——平方数之和
题目
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a 2 + b 2 = c a^2 + b^2 = c a2+b2=c。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
提示:
- 0 0 0 <= c <= 2 31 − 1 2^{31 - 1} 231−1
题解
对于给定的非负整数 c c c,需要判断是否存在整数 a a a 和 b b b,使得 a 2 + b 2 = c a^2 + b^2 = c a2+b2=c。可以枚举 a a a 和 b b b 所有可能的情况,时间复杂度为 O ( c 2 ) O(c^2) O(c2)。但是暴力枚举有一些情况是没有必要的。例如:当 c = 20 c = 20 c=20 时,当 a = 1 a = 1 a=1 的时候,枚举 b b b 的时候,只需要枚举到 b = 5 b = 5 b=5 就可以结束了,这是因为 1 2 + 5 2 = 25 > 20 1^2 + 5^2 = 25 > 20 12+52=25>20。当 b > 5 b > 5 b>5 时,一定有 1 2 + b 2 > 20 1^2 + b^2 > 20 12+b2>20。
注意到这一点,可以使用 sqrt \texttt{sqrt} sqrt 函数或者双指针降低时间复杂度。
方法一:使用标准库函数 sqrt \texttt{sqrt} sqrt
思路
在枚举 a a a 的同时,使用 sqrt \texttt{sqrt} sqrt 函数找出 b b b。注意:本题 c c c 的取值范围在 [ 0 , 2 31 − 1 ] [0,2^{31} - 1] [0,231−1],因此在计算的过程中可能会发生 int \texttt{int} int 型溢出的情况,需要使用 long \texttt{long} long 型避免溢出。
代码实现
Leetcode 官方题解:
class Solution {
public:
bool judgeSquareSum(int c) {
for (long a = 0; a * a <= c; a++) {
double b = sqrt(c - a * a);
if (b == (int)b) {
return true;
}
}
return false;
}
};
复杂度分析
时间复杂度:
O
(
c
)
O(\sqrt{c})
O(c)。枚举
a
a
a 的时间复杂度为
O
(
c
)
O(\sqrt{c})
O(c),对于每个
a
a
a 的值,可在
O
(
1
)
O(1)
O(1) 的时间内寻找
b
b
b
空间复杂度:
O
(
1
)
O(1)
O(1)
方法二:反向双指针
思路
不失一般性,可以假设 i ≤ m a x i \le max i≤max。初始时 i = 0 i = 0 i=0, m a x = c max = \sqrt{c} max=c,进行如下操作:
- 如果 m a x 2 + i 2 = c max^2 + i^2 = c max2+i2=c,我们找到了题目要求的一个解,返回 true \text{true} true;
- 如果 m a x 2 + i 2 < c max^2 + i^2 < c max2+i2<c,此时需要将 i i i 的值加 1 1 1,继续查找;
- 如果 m a x 2 + i 2 > c max^2 + i^2 > c max2+i2>c,此时需要将 m a x max max 的值减 1 1 1,继续查找。
- 当 m a x = i max = i max=i 时,结束查找,此时如果仍然没有找到整数 m a x max max 和 i i i 满足 m a x 2 + i 2 = c max^2 + i^2 = c max2+i2=c,则说明不存在题目要求的解,返回 false \text{false} false。
至于反向双指针遍历已排序好的序列的正确性(即如何确保不会漏掉其中的正确答案): 可以参考这里。
所以用反向双指针要考虑两个问题:每次移动指针排除掉了哪些情况?这些情况中是否可能包含正确答案?
代码实现
我的:
class Solution {
public:
bool judgeSquareSum(int c) {
// 找出平方根小于或等于 c 的最大整数 max
int max = sqrt(c), i = 0;
while(i <= max){
// 不用 max*max + i*i 和 c 比较,因为可能会产生类型溢出
if(c - max*max == i*i) return true;
c - max*max < i*i? max--: i++;
}
return false;
}
};
复杂度分析
时间复杂度:
O
(
c
)
O(\sqrt{c})
O(c)。最坏情况下
m
a
x
max
max 和
i
i
i 一共枚举了
0
0
0 到
c
\sqrt{c}
c 里的所有整数。
空间复杂度:
O
(
1
)
O(1)
O(1)
方法三:数学
思路
费马平方和定理告诉我们:
一个非负整数 c c c 如果能够表示为两个整数的平方和,当且仅当 c c c 的所有形如 4 k + 3 4k + 3 4k+3 的质因子的幂均为偶数。
证明请见 这里。
因此我们需要对 c c c 进行质因数分解,再判断所有形如 4 k + 3 4k + 3 4k+3 的质因子的幂是否均为偶数即可。
代码实现
Leetcode 官方题解:
class Solution {
public:
bool judgeSquareSum(int c) {
for (int base = 2; base * base <= c; base++) {
// 如果不是因子,枚举下一个
if (c % base != 0) {
continue;
}
// 计算 base 的幂
int exp = 0;
while (c % base == 0) {
c /= base;
exp++;
}
// 根据 Sum of two squares theorem 验证
if (base % 4 == 3 && exp % 2 != 0) {
return false;
}
}
// 例如 11 这样的用例,由于上面的 for 循环里 base * base <= c ,base == 11 的时候不会进入循环体
// 因此在退出循环以后需要再做一次判断
return c % 4 != 3;
}
};
复杂度分析
时间复杂度:
O
(
c
)
O(\sqrt{c})
O(c)
空间复杂度:
O
(
1
)
O(1)
O(1)