二分查找(x的平方根)(4)

https://blog.csdn.net/2601_95366422/article/details/158690947

上节课连接

一.题目

69. x 的平方根 - 力扣(LeetCode)

二.思路讲解

对于求一个非负整数 x 的算术平方根(要求返回整数部分),我们首先需要判断这个问题是否具备二分性 。所谓二分性,就是我们可以通过某种规则将搜索区间一分为二,并且能够确定目标值在哪一半。这里,算术平方根的值一定落在区间 [0, x] 内,而且这个区间是单调递增 的------随着数值增大,其平方也增大。正是这种单调性 赋予了问题二分性 ,因此我们可以采用二分查找来逼近答案。

接下来要确定使用左端点 还是右端点 模板。我们需要找到的是满足 mid * mid <= x最大 mid 值,也就是最后一个满足条件的数。左端点模板用于寻找第一个满足某个条件 的位置,而右端点模板则用于寻找最后一个满足条件 的位置。显然,本题的需求是最后一个小于等于目标 的值,因此应该采用右端点二分查找。使用右端点模板,我们可以直接定位到满足条件的最大值,即算术平方根的整数部分。

理解这一点很关键:左边界 找的是第一个,右边界找的是最后一个。根据问题是要找最大还是最小,选择合适的模板即可。

三.代码演示

cpp 复制代码
class Solution {
public:
    int mySqrt(int x)
    {
        long left = 0;
        long right = x;
        while(left < right)
        {
            long mid = left + (right - left + 1)/2;//求右端点
            long long sum = mid * mid;
            if(sum > x)
                right = mid - 1;
            else
                left = mid;
        }
        return left;
        
    }
};

四.代码讲解

一、循环条件与中点计算

采用 while (left < right) 作为循环条件,而不是 left <= right。这是因为当左右指针相遇时,区间内只剩一个元素,此时无需继续二分 ,可以直接在循环外返回该元素。这种写法可以避免死循环,并符合右端点模板的常规写法。

中点计算采用向上取整 的公式,即 mid = left + (right - left + 1) / 2。当区间长度为偶数时,中点会偏向右边 ,这确保了当 leftright 相邻时,中点指向 right,从而保证区间能够正确收缩,不会出现 left 原地不动的情况。向上取整 是右端点查找的精髓,也是避免死循环的关键。

二、区间缩进规则

在循环中,根据 mid * mid 与 x 的比较结果,按照以下规则更新指针:

  • mid * mid > x :说明 mid 的平方已经超过了 x,那么所有大于等于 mid 的数都不可能满足条件,因此最后一个满足条件的数一定在 mid 的左侧 。所以将右指针移动到 mid - 1,即舍弃右半区间

  • mid * mid <= x :说明 mid 本身是满足条件的,但右边可能还有更大的数也满足条件。因此将左指针移动到 mid,保留 mid 在搜索区间内 ,继续向右寻找更大的可行解。这里注意不能写成 left = mid + 1,因为 mid 可能是最终答案,必须保留。

三、循环结束后的处理

循环结束时,leftright 相等,此时指向的位置就是最后一个满足 mid * mid <= x 的数 ,即 x 的算术平方根的整数部分。直接返回 left 即可,无需额外判断,因为题目保证 x 是非负整数,且当 x = 0 时循环也能正确处理(区间只有 0)。

四、为什么不需要分类讨论

与之前"搜索插入位置"的右端点解法不同,本题循环结束后无需分类讨论,原因在于:我们寻找的是满足条件的最大值 ,而循环结束时 left 一定落在可行解的范围内。即使 x = 0,初始 left = 0, right = 0,循环不执行,直接返回 0,正确。当 x 为其他值时,由于我们始终在可行解区间内收缩,最终 left 必然指向满足条件的最大值。

五、与左端点模板的对比

如果错误地采用左端点模板(向下取中,缩进规则为 if (mid * mid < x) left = mid + 1; else right = mid),循环结束后 left 会指向第一个满足 mid * mid >= x 的位置 ,这可能是平方刚好等于 x 或第一个超过 x 的数,需要根据情况调整(**比如平方大于 x 则返回 left - 1,或者找到最左边的但是右边的也可以满足条件)。**虽然也能求解,但逻辑更绕,且容易出错。而右端点模板直接命中目标,更加直观。

cpp 复制代码
class Solution {
public:
    int mySqrt(int x) 
    {
        // 使用二分查找在 [0, x] 范围内寻找平方根的整数部分
        long left = 0;
        long right = x;
        
        // 标准二分查找循环
        while(left < right)
        {
            // 计算中点,防止直接 (left + right) / 2 可能溢出
            long mid = left + (right - left) / 2;
            // 计算中点的平方,使用 long long 防止乘积溢出
            long long sum = mid * mid;
            
            // 如果平方小于 x,说明 mid 可能是解,但解肯定在 [mid+1, right] 之间
            if(sum < x)
            {
                left = mid + 1;
            }
            // 如果平方大于等于 x,说明解在 [left, mid] 之间(包含 mid)
            else
            {
                right = mid;
            }
        }
        
        // 需要判断 left 是否过小,如果 (left+1)^2 <= x,说明结果应该是 left+1
        if((left+1) * (left+1) <= x)
            return left + 1;
        // 如果 left^2 > x(太大了),说明结果应该是 left-1
        else if (left * left > x)
            return left - 1;
        // 否则 left 就是正确结果
        return left;
    }
};

六、细节注意

  • 使用 long 类型定义 left、right 和 mid,是因为 x 最大可达 2^31 - 1,mid * mid 可能超出 int 范围,因此用 long long 存储乘积避免溢出。

  • 初始 right 设置为 x,对于 x = 0 或 1 也能正确工作(0 的平方根是 0,1 的平方根是 1)。

七、总结

本题是右端点二分查找的经典应用。关键在于正确运用向上取中区间缩进规则,使区间最终收敛到满足条件的最大值。掌握这一解法,不仅能解决求平方根问题,也为处理其他"最大值可行解"类问题提供了通用思路。

相关推荐
ECT-OS-JiuHuaShan2 小时前
朱梁万有递归元定理,重构《易经》
算法·重构
顶点多余2 小时前
使用C/C++语言链接Mysql详解
数据库·c++·mysql
汉克老师2 小时前
GESP2026年3月认证C++四级( 第二部分判断题(1-10))
c++·指针·函数重载·文件操作·数组·gesp4级·gesp四级
智者知已应修善业2 小时前
【51单片机独立按键控制数码管移动反向,2片74CH573/74CH273段和位,按键按下保持原状态】2023-3-25
经验分享·笔记·单片机·嵌入式硬件·算法·51单片机
khddvbe3 小时前
C++并发编程中的死锁避免
开发语言·c++·算法
C羊驼3 小时前
C语言:两天打鱼,三天晒网
c语言·经验分享·笔记·算法·青少年编程
菜菜小狗的学习笔记3 小时前
剑指Offer算法题(四)链表
数据结构·算法·链表
myloveasuka3 小时前
[Java]查找算法&排序算法
java·算法·排序算法
清水白石0083 小时前
Free-Threaded Python 实战指南:机遇、风险与 PoC 验证方案
java·python·算法