有效的完全平方数 图解题解
这道题到底在问什么
- 输入
- num = 144
- 输出
- true(12×12 = 144)
- 输入
- num = 14
- 输出
- false(3²=9,4²=16,没有整数平方等于 14)
最优解:为什么这么做
一句话答案:LeetCode 367 有效的完全平方数用二分查找:答案 x 落在 1 到 num 且越大平方越大,取 mid 比 mid×mid 与 num,相等即真、偏小抬 l、偏大压 r,时间 O(log n)、空间 O(1)。
判断 num 是不是完全平方数,到底在找什么数
给一个正整数 num,问有没有整数 x 满足 x×x=num,有就返回 true、没有返回 false,而且不许调用 sqrt 开方。题面 num=144 返回 true,12×12 正好是 144;num=14 返回 false,3²=9、4²=16,中间没有哪个整数平方等于 14。
从 1 挨个试到 num,会白算掉多长一截
逐个试值:让 x 从 1 数到 num,每个都平方看等不等于 num,等于就 true、否则 false。可 num 若接近 20 亿,最坏得试上十几亿次,时间 O(n)(n 是 num 的大小)。sqrt 又被禁用,挨个试根本走不动。
平方只增不减,半截候选可以整段跳过
关键在平方本身有序:x 越大,x×x 越大,绝不回头。把候选 x 从 1 排到 num,拿正中间的候选 mid 平方后和 num 比:mid×mid 比 num 小,mid 和它左边所有候选都够不着,左半连 mid 一起扔;比 num 大,右半全超了、一起扔。一比就砍掉一半候选,这就是二分查找,每轮砍半往剩下半边找。
取中点比完平方,该往哪半收、循环停在哪
用两个指针 l、r 圈住还没排除的候选,起点 l=1、r=num,两端都算数,是闭区间 [l,r]。每轮取中点 mid=(l+r)//2,算 sq=mid×mid 和 num 比:相等就返回 true;sq 偏小,x 在右边,l 抬到 mid+1;sq 偏大,x 在左边,r 压到 mid-1。收缩都要跨过 mid,留着已验过的它就缩不动。循环写 while l<=r,好让 l、r 撞到同一格时再比一次;l 越过 r 还没相等就返回 false。
num=144 逐轮列出 mid 和它的平方
起点 l=1、r=144。第一轮 mid=72,72×72=5184>144 偏大,r 压到 71。第二轮 mid=36,36×36=1296>144,r 到 35。第三轮 mid=18,18×18=324>144,r 到 17。第四轮 mid=9,9×9=81<144 偏小,l 抬到 10。第五轮 l=10、r=17,mid=13,13×13=169>144,r 到 12。第六轮 mid=11,11×11=121<144,l 抬到 12。第七轮 l=r=12,mid=12,12×12=144 相等,返回 true,七轮就从 [1,144] 逼到了 x=12。动画把范围缩到 1..14 演示,逼近道理一样。
mid×mid 冲破 int 上限,比较从此全反
每轮范围减半,最多约 log₂n 轮(n 是 num 大小),时间 O(log n)、空间 O(1)。最容易埋雷的是 mid×mid:num 接近 2³¹ 时平方冲破 32 位 int 上限、溢出成负数,本该偏大的判成偏小、方向反过来,得用 64 位 long 存 sq,或把比较改成 mid>num/mid 避开乘法。循环漏成 while l<r,l、r 撞到答案那格时直接跳出、少比一次,完全平方数会被误判成 false;偏小时只写 l=mid 不加 1,范围缩不动就死循环。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住三件事:mid×mid == num 就中了;偏小往右找(抬 l);偏大往左找(压 r)。下面用 num=144 把每一轮砍半演给你看。
- 4把候选答案 1 到 14 排成一排(格子里是候选值 x)。左指针 l 在最左、右指针 r 在最右,搜索范围就是它俩之间。
- 5第 1 轮。在范围 [1, 14] 的正中间取出候选 x = 7(mid 指着它)。
- 6把这个候选平方:7×7 = 49。接下来拿 49 跟目标 144 比大小。
- 7比较这一对:49 < 144。平方还偏小。
- 849 比 144 小,说明 7 太小了,真正的 x 在它右边。把左指针抬到 mid 右边一格。
- 9左指针 l 跳到下标 7(候选 8)。左边变灰的那些已被排除,搜索范围砍掉了一半。
- 10第 2 轮。在范围 [8, 14] 的正中间取出候选 x = 11(mid 指着它)。
- 11把这个候选平方:11×11 = 121。接下来拿 121 跟目标 144 比大小。
- 12比较这一对:121 < 144。平方还偏小。
- 13121 比 144 小,说明 11 太小了,真正的 x 在它右边。把左指针抬到 mid 右边一格。
- 14左指针 l 跳到下标 11(候选 12)。左边变灰的那些已被排除,搜索范围砍掉了一半。
- 15第 3 轮。在范围 [12, 14] 的正中间取出候选 x = 13(mid 指着它)。
- 16把这个候选平方:13×13 = 169。接下来拿 169 跟目标 144 比大小。
- 17比较这一对:169 > 144。平方偏大了。
- 18169 比 144 大,说明 13 太大了,真正的 x 在它左边。把右指针压到 mid 左边一格。
- 19右指针 r 退到下标 11(候选 12)。右边变灰的那些已被排除,搜索范围又砍掉一半。
- 20第 4 轮。在范围 [12, 12] 的正中间取出候选 x = 12(mid 指着它)。
- 21把这个候选平方:12×12 = 144。接下来拿 144 跟目标 144 比大小。
- 22比较这一对:144 == 144。相等,命中!
- 23正好相等!12 的平方就是 144,说明 144 是完全平方数,立刻返回 true。
- 24二分查找在范围里精准命中了 x = 12。144 确实是完全平方数,最终答案 true。
⚠️ 容易写错的地方
✗ 错:用 int 存 mid×mid,大数溢出变负
✓ 对:mid×mid 用 long(或先判 mid > num/mid)
num 接近 2³¹ 时,mid×mid 超出 int 范围会溢出成负数,比较结果全错
✗ 错:循环写成 while l < r,漏掉 l==r 那一格
✓ 对:写 while l <= r,让左右重合时也比一次
答案可能恰好落在 l 和 r 重合的那个位置,少比一次就会漏掉它返回 false
✗ 错:偏小时只写 l = mid(不加 1)
✓ 对:偏小 l = mid+1、偏大 r = mid-1
不挪过 mid 会让范围卡住不再缩小,造成死循环
完整代码(Python / C++ / Java)
Python
def isPerfectSquare(num):
l, r = 1, num # 答案落在 1..num
while l <= r:
mid = (l + r) // 2
sq = mid * mid
if sq == num: # 正好相等:找到了
return True
elif sq < num: # 偏小:往右找
l = mid + 1
else: # 偏大:往左找
r = mid - 1
return FalseC++
bool isPerfectSquare(int num){
long l = 1, r = num;
while (l <= r) {
long mid = (l + r) / 2;
long sq = mid * mid; // long 防溢出
if (sq == num) return true;
else if (sq < num) l = mid + 1;
else r = mid - 1;
}
return false;
}Java
public boolean isPerfectSquare(int num) {
long l = 1, r = num;
while (l <= r) {
long mid = (l + r) / 2;
long sq = mid * mid; // long 防溢出
if (sq == num) return true;
else if (sq < num) l = mid + 1;
else r = mid - 1;
}
return false;
}复杂度
时间
O(log n)
每轮把搜索范围砍一半,n 是 num 的大小,比从 1 试到 num 的 O(n) 快得多
空间
O(1)
只用 l、r、mid 几个变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 有效的完全平方数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么二分的下界从 1 起,不从 0 开始?+
题目说 num 是正整数,最小的完全平方数就是 1×1=1,答案不可能比 1 还小,下界搁 1 刚好;从 0 起只是白搭一格 0×0=0,永远等不到正的 num。上界取 num 也够用,x 到了 2 以后,x×x 已经比 num 大得多,真正的答案绝不会超过 num 本身。
不让用 sqrt,除了二分还有别的解法吗?+
有两条。一条是连续奇数求和:前 k 个奇数 1、3、5、7… 加起来正好是 k²,所以从 num 里不断减去 1、3、5、7…,能干干净净减到 0 就是完全平方数,减到负数就不是。另一条是牛顿迭代法,用逼近公式反复往平方根上收。两条都行,但二分的收缩规则最直观、最不容易写错。
mid×mid 会溢出,具体怎么防?+
两种防法。一是把 sq 用 64 位的 long 存,mid 顶多到 num≈2³¹,平方约 2⁶²,long 装得下;二是干脆不做乘法,把 sq
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 有效的完全平方数 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。