一、题目描述
给你一个正整数 num。如果 num 是一个完全平方数,则返回 true,否则返回 false。
完全平方数是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt。
示例 1:
输入: num = 16
输出: true
解释: 返回 true,因为 4 * 4 = 16 且 4 是一个整数。
示例 2:
输入: num = 14
输出: false
解释: 返回 false,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
二、解题思路
- 题目限制不能使用内置
sqrt函数,需实现高效判断,最优解为二分查找 ,时间复杂度O(log n)。 - 核心需求:在
[0, num]的整数范围内,寻找是否存在整数mid,使得mid² = num。 - 关键处理:
mid*mid可能超出int范围导致溢出,需强转为long类型避免结果误判。
三、代码实现(Java)
解法1:
class Solution {
public boolean isPerfectSquare(int num) {
// 二分查找的左右边界,初始覆盖0到num的所有整数
int start=0;
int end=num;
while(start<=end){
// 计算中间值,避免直接start+end相加导致int溢出
int mid=start+(end-start)/2;
// 强转为long,防止mid*mid超出int范围导致结果溢出误判
if((long) mid*mid==num){
// 找到满足条件的mid,直接返回true
return true;
}else if((long) mid*mid<num){
// mid的平方小于num,目标值在右半区间,更新左边界
start=mid+1;
}else{
// mid的平方大于num,目标值在左半区间,更新右边界
end=mid-1;
}
}
// 循环结束未找到符合条件的mid,说明不是完全平方数
return false;
}
}
也可以用 【LeetCode 刷题笔记】34. 在排序数组中查找元素的第一个和最后一个位置 | 二分查找经典刷题题解这篇文章的思路,本质也还是二分查找。
解法2:代码如下:
class Solution {
public boolean isPerfectSquare(int num) {
// 特殊情况:num=1时直接返回true,避免后续处理边界问题
if(num == 1){
return true;
}
// 调用自定义二分实现的sqrt函数,获取num的算术平方根整数部分,转为long防止溢出
long a = (long) sqrt(num);
// 用a*a==num判断是否为完全平方数,必须用long类型防止溢出
if(a * a == num){
return true;
}
return false;
}
/**
* 用二分查找实现整数平方根,返回x的算术平方根的整数部分
* @param x 待求平方根的整数
* @return x的算术平方根的整数部分
*/
public int sqrt(int x){
// 二分查找区间[1, x],初始左右边界
int left = 1;
int right = x;
int mid = 0;
while(left <= right){
// 计算中间值,避免直接left+right相加导致int溢出
mid = left + (right - left)/2;
// 用mid <= x/mid代替mid*mid <=x,防止mid*mid超出int范围溢出
if(mid <= x / mid){
// mid的平方小于等于x,目标值在右半区间,更新左边界
left = mid + 1;
}else{
// mid的平方大于x,目标值在左半区间,更新右边界
right = mid - 1;
}
}
// 循环结束时right即为x的算术平方根的整数部分
return right;
}
}
解法2注意事项:判断条件的选择:为什么只能用a * a == num?
在sqrt函数中,我们返回的就是num的算术平方根的整数部分,若使用num / a == a判断,会因Java整数除法自动抹除小数部分导致误判。例如num=17时,sqrt(17)返回4,num/a=17/4=4,此时num/a == a会误判为true,但44=16≠17,并非完全平方数;
而使用a * a == num时,44=16≠17,会正确返回false,只有当num是完全平方数时才会成立,无任何误判风险。
四、核心笔记 & 易错点解析
1. 整数溢出问题(高频踩坑点)
mid为int类型时,mid*mid的结果可能超出int的最大值(2147483647),导致结果溢出变为负数,影响判断逻辑。
解决方案:将mid强转为long后再计算乘积,即(long) mid * mid,从根源避免溢出问题。
2. 二分查找边界处理
初始边界:start=0、end=num,覆盖所有可能的整数解(包括num=1、num=0的特殊情况)。
循环条件:start <= end,确保区间内所有元素都被遍历,不会漏掉边界值(如num=1时,mid=1需被处理)。
区间更新逻辑:
- 若
mid² == num:直接返回true,无需继续查找; - 若
mid² < num:目标值更大,需向右半区间查找,更新start=mid+1; - 若
mid² > num:目标值更小,需向左半区间查找,更新end=mid-1。
3. 特殊情况验证
num=1时,mid=1,1*1=1,直接返回true;num=2时,循环结束未找到符合条件的mid,返回false,符合预期;num=2147483647(int最大值)时,强转long的处理能避免溢出,保证判断正确。
五、复杂度分析
- 时间复杂度 :
O(log n),二分查找每次将区间缩小一半,最多循环log₂(num)次,符合高效判断的要求。 - 空间复杂度 :
O(1),仅使用常数级别的额外变量,无额外空间开销。
六、总结
本题核心是利用二分查找替代内置sqrt函数,高效判断完全平方数,关键在于处理int溢出问题和二分边界逻辑。
这类"无内置函数的平方根/平方数判断"问题,二分查找是最优解,掌握区间更新和溢出处理是解题关键。