LC 633. 平方数之和

题目描述

这是 LeetCode 上的 633. 平方数之和 ,难度为 中等

给定一个非负整数 c,你要判断是否存在两个整数 ab,使得 $a^2 + b^2 = c$。

示例 1:

1
2
3
4
5
输入:c = 5

输出:true

解释:1 * 1 + 2 * 2 = 5

示例 2:
1
2
3
输入:c = 3

输出:false

示例 3:
1
2
3
输入:c = 4

输出:true

示例 4:
1
2
3
输入:c = 2

输出:true

示例 5:
1
2
3
输入:c = 1

输出:true

提示:

  • $0 <= c <= 2^{31} - 1$

基本分析

根据等式 $a^2 + b^2 = c$,可得知 ab 的范围均为 $[0,\sqrt{c}]$。

基于此我们会有以下几种做法。


枚举

我们可以枚举 a ,边枚举边检查是否存在 b 使得等式成立。

这样做的复杂度为 $O(\sqrt{c})$。

Java 代码:

1
2
3
4
5
6
7
8
9
10
class Solution {
public boolean judgeSquareSum(int c) {
int max = (int)Math.sqrt(c);
for (int a = 0; a <= max; a++) {
int b = (int)Math.sqrt(c - a * a);
if (a * a + b * b == c) return true;
}
return false;
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool judgeSquareSum(int c) {
int maxv = sqrt(c);
for (int a = 0; a <= maxv; a++) {
int b = sqrt(c - a * a);
if (a * a + b * b == c) return true;
}
return false;
}
};

Python 代码:
1
2
3
4
5
6
7
8
class Solution:
def judgeSquareSum(self, c: int) -> bool:
maxv = int(math.sqrt(c))
for a in range(maxv + 1):
b = int(math.sqrt(c - a * a))
if a * a + b * b == c:
return True
return False

TypeScript 代码:
1
2
3
4
5
6
7
8
function judgeSquareSum(c: number): boolean {
const maxv = Math.floor(Math.sqrt(c));
for (let a = 0; a <= maxv; a++) {
const b = Math.floor(Math.sqrt(c - a * a));
if (a * a + b * b === c) return true;
}
return false;
};

  • 时间复杂度:$O(\sqrt{c})$
  • 空间复杂度:$O(1)$

双指针

由于 ab 的范围均为 $[0,\sqrt{c}]$,因此我们可以使用「双指针」在 $[0,\sqrt{c}]$ 范围进行扫描:

  • $a^2 + b^2 = c$ : 找到符合条件的 ab,返回 $true$
  • $a^2 + b^2 < c$ : 当前值比目标值要小,a++
  • $a^2 + b^2 > c$ : 当前值比目标值要大,b--

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public boolean judgeSquareSum(int c) {
int a = 0, b = (int)Math.sqrt(c);
while (a <= b) {
int cur = a * a + b * b;
if (cur == c) return true;
else if (cur > c) b--;
else a++;
}
return false;
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
bool judgeSquareSum(int c) {
int a = 0, b = sqrt(c);
while (a <= b) {
long cur = a * a * 1L + b * b;
if (cur == c) return true;
else if (cur > c) b--;
else a++;
}
return false;
}
};

Python 代码:
1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def judgeSquareSum(self, c: int) -> bool:
a, b = 0, int(math.sqrt(c))
while a <= b:
cur = a * a + b * b
if cur == c:
return True
elif cur > c:
b -= 1
else:
a += 1
return False

TypeScript 代码:
1
2
3
4
5
6
7
8
9
10
function judgeSquareSum(c: number): boolean {
let a = 0, b = Math.floor(Math.sqrt(c));
while (a <= b) {
let cur = a * a + b * b;
if (cur === c) return true;
else if (cur > c) b--;
else a++;
}
return false;
};

  • 时间复杂度:$O(\sqrt{c})$
  • 空间复杂度:$O(1)$

费马平方和

费马平方和 : 奇质数能表示为两个平方数之和的充分必要条件是该质数被 4 除余 1 。

翻译过来就是:当且仅当一个自然数的质因数分解中,满足 4k+3 形式的质数次方数均为偶数时,该自然数才能被表示为两个平方数之和。

因此我们对 c 进行质因数分解,再判断满足 4k+3 形式的质因子的次方数是否均为偶数即可。

Java 代码:

1
2
3
4
5
6
7
8
9
public class Solution {
public boolean judgeSquareSum(int c) {
for (int i = 2, cnt = 0; i * i <= c; i++, cnt = 0) {
while (c % i == 0 && ++cnt > 0) c /= i;
if (i % 4 == 3 && cnt % 2 != 0) return false;
}
return c % 4 != 3;
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
class Solution {
public:
bool judgeSquareSum(int c) {
for (int i = 2, cnt = 0; i * i <= c; i++, cnt = 0) {
while (c % i == 0 && ++cnt > 0) c /= i;
if (i % 4 == 3 && cnt % 2 != 0) return false;
}
return c % 4 != 3;
}
};

Python 代码:
1
2
3
4
5
6
7
8
9
10
class Solution:
def judgeSquareSum(self, c: int) -> bool:
for i in range(2, int(c**0.5) + 1, 1):
cnt = 0
while c % i == 0:
c //= i
cnt += 1
if i % 4 == 3 and cnt % 2 != 0:
return False
return c % 4 != 3

TypeScript 代码:
1
2
3
4
5
6
7
function judgeSquareSum(c: number): boolean {
for (let i = 2, cnt = 0; i * i <= c; i++, cnt = 0) {
while (c % i === 0 && ++cnt > 0) c /= i;
if (i % 4 === 3 && cnt % 2 !== 0) return false;
}
return c % 4 !== 3;
};

  • 时间复杂度:$O(\sqrt{c})$
  • 空间复杂度:$O(1)$

我猜你问

  • 三种解法复杂度都一样,哪个才是最优解呀?

前两套解法是需要「真正掌握」的,而「费马平方和」更多的是作为一种拓展。

你会发现从复杂度上来说,其实「费马平方和」并没有比前两种解法更好,但由于存在对 c 除质因数操作,导致「费马平方和」实际表现效果要优于同样复杂度的其他做法。但这仍然不成为我们必须掌握「费马平方和」的理由。

三者从复杂度上来说,都是 $O(\sqrt{c})$ 算法,不存在最不最优的问题。

  • 是否有关于「费马平方和」的证明呢?

想要看 莱昂哈德·欧拉 对于「费马平方和」的证明在 这里,我这里直接引用 费马 本人的证明:

我确实发现了一个美妙的证明,但这里空白太小写不下。

  • 我就是要学「费马平方和」,有没有可读性更高的代码?

有的,在这里。喜欢的话可以考虑背过:

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
public boolean judgeSquareSum(int c) {
for (int i = 2; i * i <= c; i++) {
int cnt = 0;
while (c % i == 0) {
cnt++;
c /= i;
}
if (i % 4 == 3 && cnt % 2 != 0) return false;
}
return c % 4 != 3;
}
}


最后

这是我们「刷穿 LeetCode」系列文章的第 No.633 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!