OJ链接:367. 有效的完全平方数 - 力扣(LeetCode)
引言:
当我们面对这道题目时,首先要理解什么是完全平方数。完全平方数是指一个数能够被某个整数的平方表示,也就是说,它可以写成两个相同的因数相乘。解决这个问题的关键在于如何判断一个给定的正整数是否是完全平方数,而且题目要求不能使用内置库函数如 sqrt。那我们比较简单的方法呢就是暴力求解,从头开始遍历一遍就能知道是不是完全平方数了,要是这样做呢,这道题目就失去了锻炼的意义了。这里我们有两种思路,第一种呢使用二分查找的思想,第二种使用一些数学上的技巧。
思路一:二分查找
bool isPerfectSquare(int num)
{
int left = 1; // 这里我让它从1开始是因为题目说了n是正整数,不过从0开始也是可以的
int right = num;
int mid;
while (left <= right)
{
mid = left + (right -left) / 2; // 防止溢出
if ((long)mid * mid < num) // 这里强制类型转换成long类型,防止溢出
left = mid + 1;
else if ((long)mid * mid > num)
right = mid - 1;
else
return true;
}
return false;
}
我们要找的那个平方根就在0到num的范围里,我们使用更新中间值的方法来每次缩小一半的查找方法,如果中间值的平方比该数字大,那就说明要找的中间值应该是在左半边区间的,这时候我们让 right 的值变成 mid - 1,因为接下来查找的区间肯定不包括 mid 了,然后就能更新 mid 的值,继续比较,直到找到平方根了就返回,或者说当 left 比 right 大了,这时候就找不到了,也返回。
如果说不想去担心溢出问题的话,可以用下面这一种写法,思路也是二分查找,只是变成了用除法来判断。这样就不用担心溢出问题。
bool isPerfectSquare(int num)
{
int left = 1;
int right = num;
int mid;
while (left <= right)
{
mid = left + (right - left) / 2;
if (num / mid == mid && num % mid == 0)
return true;
else if (num / mid < mid)
{
right = mid - 1;
}
else
left = mid + 1;
}
return false;
}
注意这里不能在 num / mid == mid 的时候就直接返回 true,因为整数的除法是直接截断小数点部分的,一个数可能在数学层面上是2.5,但是在这里它就是2,所以我们多加一个取模的值为0的判断,这样就杜绝了一些数来滥竽充数了。
这种二分查找的思路,它的时间复杂度是:O(logN)
思路二:数学性质
bool isPerfectSquare(int num)
{
int n = 0;
while (num > 0)
{
num -= (2 * n + 1);
n++;
}
if (num == 0)
return true;
return false;
}
完全平方数是一系列奇数之和。例如:1=1, 4=1+3, 9=1+3+5, 16=1+3+5+7 …
这样的话,我们可以遍历奇数,不断让 num 减去这些奇数,看最终是否能够减到0。如果刚好减到0就说明是完全平方数,不然就不是了。
这种思路就比较巧妙。它的时间复杂度是:O(√n)
总结:
在解决这个问题的过程中,我们通过二分查找和数学性质两种不同的方法来判断一个正整数是否是完全平方数。这两种方法各有优劣,选择哪一种取决于具体的应用场景和个人的偏好。通过理解这道题目,我们可以更深入地了解二分查找的应用和数学性质在算法中的作用。希望本篇博客能够帮助你更好地理解并解决这个问题。