LeetCode 367. 有效的完全平方数
问题描述
给定一个 正整数 num,判断它是否是一个完全平方数(即是否存在某个整数 x,使得 x² = num)。
要求:不要使用任何内置的库函数,例如 sqrt。
示例:
输入: num = 16
输出: true
解释: 4 * 4 = 16
输入: num = 14
输出: false
解释: 14 不是完全平方数
约束条件:
1 <= num <= 2³¹ - 1
算法思路
方法一:二分查找
核心思想:完全平方数的平方根一定在 [1, num] 范围内,使用二分查找快速定位。
步骤:
- 设置左边界
left = 1,右边界right = num - 当
left <= right时:- 计算中点
mid = left + (right - left) / 2 - 计算
mid²并与num比较 - 如果
mid² == num,返回true - 如果
mid² < num,说明平方根在右半部分,left = mid + 1 - 如果
mid² > num,说明平方根在左半部分,right = mid - 1
- 计算中点
- 如果循环结束还没找到,返回
false
注意:计算 mid² 时要考虑整数溢出问题,可以使用 long 类型。
方法二:牛顿迭代法(数学)
核心思想:使用牛顿迭代公式求平方根,然后验证结果。
牛顿迭代公式:x_{n+1} = (x_n + num / x_n) / 2
步骤:
- 初始化
x = num - 迭代计算直到收敛:
x = (x + num / x) / 2 - 验证最终的
x是否满足x² == num
方法三:数学(奇数和)
核心思想:完全平方数可以表示为前 n 个奇数的和:
1 = 14 = 1 + 39 = 1 + 3 + 516 = 1 + 3 + 5 + 7- …
步骤:
- 初始化
odd = 1 - 不断从
num中减去奇数:num -= odd - 如果
num == 0,说明是完全平方数 - 如果
num < 0,说明不是完全平方数
代码实现
方法一:二分查找
class Solution {
/**
* 判断一个正整数是否为完全平方数(二分查找)
*
* @param num 正整数
* @return 如果是完全平方数返回true,否则返回false
*/
public boolean isPerfectSquare(int num) {
// 边界情况处理
if (num == 1) {
return true;
}
long left = 1; // 左边界,使用long避免溢出
long right = num; // 右边界
while (left <= right) {
long mid = left + (right - left) / 2; // 防止溢出的中点计算
long square = mid * mid; // 计算mid的平方
if (square == num) {
return true; // 找到平方根
} else if (square < num) {
left = mid + 1; // 平方根在右半部分
} else {
right = mid - 1; // 平方根在左半部分
}
}
return false; // 未找到
}
}
方法二:牛顿迭代
class Solution {
/**
* 判断一个正整数是否为完全平方数(牛顿迭代)
*
* @param num 正整数
* @return 如果是完全平方数返回true,否则返回false
*/
public boolean isPerfectSquare(int num) {
if (num == 1) {
return true;
}
long x = num; // 使用long避免溢出
// 牛顿迭代:x = (x + num/x) / 2
while (x * x > num) {
x = (x + num / x) / 2;
}
return x * x == num;
}
}
方法三:数学(奇数和)
class Solution {
/**
* 判断一个正整数是否为完全平方数(奇数和)
* 利用性质:n² = 1 + 3 + 5 + ... + (2n-1)
*
* @param num 正整数
* @return 如果是完全平方数返回true,否则返回false
*/
public boolean isPerfectSquare(int num) {
int odd = 1; // 从第一个奇数开始
// 不断减去奇数
while (num > 0) {
num -= odd;
odd += 2; // 下一个奇数
}
// 如果正好减到0,说明是完全平方数
return num == 0;
}
}
算法分析
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 二分查找 | O(log n) | O(1) |
| 牛顿迭代 | O(log log n) | O(1) |
| 奇数和 | O(√n) | O(1) |
算法过程
num = 16 :
二分查找:
left = 1, right = 16mid = 8, square = 64 > 16→right = 7mid = 4, square = 16 == 16→ 返回true
牛顿迭代:
x = 16x = (16 + 16/16) / 2 = 8x = (8 + 16/8) / 2 = 5x = (5 + 16/5) / 2 = 44² = 16 == 16→ 返回true
奇数和:
num = 16 - 1 = 15num = 15 - 3 = 12num = 12 - 5 = 7num = 7 - 7 = 0→ 返回true
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准完全平方数
System.out.println("Test 1 (16): " + solution.isPerfectSquare(16)); // true
// 测试用例2:非完全平方数
System.out.println("Test 2 (14): " + solution.isPerfectSquare(14)); // false
// 测试用例3:边界情况-1
System.out.println("Test 3 (1): " + solution.isPerfectSquare(1)); // true
// 测试用例4:大完全平方数
System.out.println("Test 4 (10000): " + solution.isPerfectSquare(10000)); // true
// 测试用例5:大非完全平方数
System.out.println("Test 5 (2147395600): " + solution.isPerfectSquare(2147395600)); // true (46340²)
// 测试用例6:接近最大值
System.out.println("Test 6 (2147483647): " + solution.isPerfectSquare(2147483647)); // false
// 测试用例7:小完全平方数
System.out.println("Test 7 (4): " + solution.isPerfectSquare(4)); // true
System.out.println("Test 8 (9): " + solution.isPerfectSquare(9)); // true
System.out.println("Test 9 (25): " + solution.isPerfectSquare(25)); // true
// 测试用例8:质数(非完全平方数)
System.out.println("Test 10 (17): " + solution.isPerfectSquare(17)); // false
}
关键点
-
整数溢出:
- 计算
mid * mid时可能溢出,使用long类型 - 中点计算使用
left + (right - left) / 2避免溢出
- 计算
-
边界情况:
num = 1是完全平方数(1² = 1)- 题目保证
num >= 1,无需处理负数
-
数学:
- 完全平方数的平方根一定在
[1, num]范围内 - 对于大数,平方根远小于原数,所以二分查找效率很高
- 完全平方数的平方根一定在
常见问题
-
为什么不用
Math.sqrt()?- 题目明确要求不能使用内置库函数
-
二分查找的边界条件怎么确定?
- 左边界从 1 开始(因为 num ≥ 1)
- 右边界可以优化为
num/2 + 1,对于大数优化效果有限
-
牛顿迭代法为什么有效?
- 牛顿迭代法是求解方程 f(x) = 0 的数值方法
- 对于求平方根,等价于求解 x² - num = 0
-
如何处理整数溢出?
- 使用
long类型存储中间结果
- 使用
1906

被折叠的 条评论
为什么被折叠?



