【LeetCode 刷题笔记】367.有效的完全平方数 | 二分查找经典刷题题解

一、题目描述

给你一个正整数 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)返回4num/a=17/4=4,此时num/a == a会误判为true,但44=16≠17,并非完全平方数;

而使用a * a == num时,44=16≠17,会正确返回false,只有当num是完全平方数时才会成立,无任何误判风险。


四、核心笔记 & 易错点解析

1. 整数溢出问题(高频踩坑点)

midint类型时,mid*mid的结果可能超出int的最大值(2147483647),导致结果溢出变为负数,影响判断逻辑。

解决方案:将mid强转为long后再计算乘积,即(long) mid * mid,从根源避免溢出问题。

2. 二分查找边界处理

初始边界:start=0end=num,覆盖所有可能的整数解(包括num=1num=0的特殊情况)。

循环条件:start <= end,确保区间内所有元素都被遍历,不会漏掉边界值(如num=1时,mid=1需被处理)。

区间更新逻辑:

  • mid² == num:直接返回true,无需继续查找;
  • mid² < num:目标值更大,需向右半区间查找,更新start=mid+1
  • mid² > num:目标值更小,需向左半区间查找,更新end=mid-1

3. 特殊情况验证

  • num=1时,mid=11*1=1,直接返回true
  • num=2时,循环结束未找到符合条件的mid,返回false,符合预期;
  • num=2147483647(int最大值)时,强转long的处理能避免溢出,保证判断正确。

五、复杂度分析

  • 时间复杂度O(log n),二分查找每次将区间缩小一半,最多循环log₂(num)次,符合高效判断的要求。
  • 空间复杂度O(1),仅使用常数级别的额外变量,无额外空间开销。

六、总结

本题核心是利用二分查找替代内置sqrt函数,高效判断完全平方数,关键在于处理int溢出问题和二分边界逻辑。

这类"无内置函数的平方根/平方数判断"问题,二分查找是最优解,掌握区间更新和溢出处理是解题关键。

相关推荐
一切皆是因缘际会9 小时前
从概率拟合到内生心智:2026 下一代 AI 架构演进与落地实践
人工智能·深度学习·算法·架构
Java成神之路-9 小时前
【LeetCode 刷题笔记】34. 在排序数组中查找元素的第一个和最后一个位置 | 二分查找经典刷题题解
算法·leetcode
不忘不弃9 小时前
用BFS方法求解平分汽油问题
算法·宽度优先
AI科技星9 小时前
全域数学·72分册·射影原本 无穷维射影几何卷细化子目录【乖乖数学】
人工智能·线性代数·算法·机器学习·数学建模·数据挖掘·量子计算
风落无尘9 小时前
《智能重生:从垃圾堆到AI工程师》——第四章 变化的艺术
人工智能·线性代数·算法
JAVA面经实录9179 小时前
计算机基础(完整版·超详细可背诵)
java·linux·数据结构·算法
AC赳赳老秦9 小时前
知识产权辅助:用 OpenClaw 批量生成专利交底书 / 软著申请材料,自动校验格式与内容合规性
java·人工智能·python·算法·elasticsearch·deepseek·openclaw
WBluuue10 小时前
Codeforces 1093 Div2(ABCD1D2)
c++·算法
浅念-10 小时前
「一文吃透 BFS:从层序遍历到锯齿形、最大宽度、每层最大值」
数据结构·算法