当前位置: 首页 > news >正文

Leetcode二分查找(5)

69. x 的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

解题思路总览

  1. 二分查找(搜索平方 <= x 的最大整数)
  2. 牛顿迭代法(Newton / Heron)
  3. 位运算按位试商法(Binary Digit / Bit-by-Bit method)
  4. 连续减去奇数法(数学性质,对比用,最坏 O(√x))
  5. 线性扫描或暴力枚举(仅用于错误示例/对比)

满足题目 O(log x) 要求的:1(二分)、2(牛顿平均情况下很快,迭代次数 ~ O(log log x) 数值方法)、3(固定 ~ 16 或 32 次迭代)。方法4、5 不满足最坏 O(log x)(示例展示思路差异)。


思路一:二分查找

原理与适用场景:
我们寻找最大整数 r 使得 r^2 <= x。答案一定在 [0, x](更紧的上界可用 min(x, 46340) 对 int 而言;但直接用 x 也安全)。用二分在 [0, x] 中查找:若 mid^2 <= x 则 mid 可能是答案,移动左边界;否则收缩右边界。注意 mid*mid 可能溢出,需要用 long。
适合任何需要在单调函数上取边界的场景,是最直接稳健模板。

实现步骤:

  1. 特判 x < 2 直接返回 x。
  2. l=1, r=x/2(因为平方根不会超过 x/2 当 x>=2;也可用 x 但更宽)。
  3. while(l <= r) 取 mid。
  4. 若 mid^2 == x 返回 mid。
  5. 若 mid^2 < x 记录 ans=mid, l=mid+1。
  6. 否则 r=mid-1。
  7. 返回 ans。

JAVA 代码实现:

public class SqrtBinary {public int mySqrt(int x) {if (x < 2) {                 // 0 和 1 的平方根就是自身return x;                // 直接返回}int l = 1;                   // 左边界(1 开始)int r = x / 2;               // 右边界(平方根不会超过 x/2 当 x>=2)int ans = 1;                 // 记录满足条件的最大值while (l <= r) {             // 闭区间二分int mid = l + (r - l) / 2;      // 取中long sq = (long) mid * mid;     // 转 long 防溢出if (sq == x) {           // 刚好平方等于 xreturn mid;          // 直接返回} else if (sq < x) {     // mid 仍然可行,尝试更大ans = mid;           // 更新答案l = mid + 1;         // 向右侧继续} else {                 // sq > x 需要缩小r = mid - 1;         // 收缩右边界}}return ans;                  // 返回平方 <= x 的最大整数}
}

思路二:牛顿迭代法(Newton / Heron)

原理与适用场景:
求解方程 f(y)=y^2 - x = 0。Newton 迭代公式:y_{k+1} = (y_k + x / y_k) / 2。当 y_k 收敛后即为 sqrt(x)。由于我们要的是整数部分,可迭代到 y_{k+1} >= y_k 或两次迭代差值 < 1 时停止,然后向下取整。牛顿法收敛二次速度很快,迭代次数极少(对 32 位整数一般 < 8 次)。
需要注意:使用 long 避免 x / y 溢出;终止条件谨慎设置防止死循环。

实现步骤:

  1. x < 2 返回 x。
  2. 取初始估计 y = x(或 x/2+1 更紧)。
  3. while (y*y > x) 更新 y = (y + x / y)/2。
  4. 循环结束 y 即 floor(sqrt(x))。

JAVA 代码实现:

public class SqrtNewton {public int mySqrt(int x) {if (x < 2) {                     // 0/1 直接返回return x;                    // 返回本身}long y = x;                      // 初始估计(long 防溢出)while (y * y > x) {              // 还未收敛到平方 <= xy = (y + x / y) / 2;         // Newton 迭代公式}return (int) y;                  // y 已经是整数部分}
}

思路三:位运算按位试商法(逐位构造平方根)

原理与适用场景:
从结果的最高可能位开始尝试设置,若设置该位后 candidate^2 <= x 则保留该位,否则清除。与“二进制开方”或“构造数字”类似。32 位整数的平方根最多 16 位(因为 2^16=65536 > sqrt(Integer.MAX_VALUE) ≈ 46340),因此迭代 16~32 次即可。
该方法时间 O(log x),常数小,完全整数运算,适合对位运算有要求或在无除法环境下(可用加法和移位判断平方)略作改造。

实现步骤:

  1. ans=0。
  2. 选择最高试探位 bit = 1<<15(因为 2^15=32768;再高一位 1<<16=65536 平方已超过 int 最大 sqrt 范围)。亦可从 1<<16 开始检测安全范围。
  3. 从高到低:
    • temp = ans | bit。
    • 若 (long)temp * temp <= x,则 ans = temp。
    • bit >>= 1。
  4. 返回 ans。

JAVA 代码实现:

public class SqrtBitwise {public int mySqrt(int x) {if (x < 2) {                // 小数特判return x;               // 直接返回}int ans = 0;                // 当前构造的结果int bit = 1 << 15;          // 从第 15 位开始尝试(安全上界)while (bit > 0) {           // 逐位下降int candidate = ans | bit;           // 尝试把该位设为 1long sq = (long) candidate * candidate; // 计算平方if (sq <= x) {          // 若平方仍不超过 xans = candidate;    // 接受该位}bit >>= 1;              // 继续下一位}return ans;                 // 已构造出最大平方 <= x 的值}
}

思路四:连续减去奇数法(数学性质,对比)

原理与适用场景:
利用 1 + 3 + 5 + … + (2n-1) = n^2。不断从 x 中依次减去递增奇数,能减多少次 n 即 floor(sqrt(x))。但需要 O(√x) 次循环,无法满足 O(log x) 要求,仅作数学性质展示或当 x 很小(例如 < 10^6)且代码极简的情况。

实现步骤:

  1. count=0, odd=1。
  2. while x >= odd: x -= odd; odd += 2; count++。
  3. 返回 count。

JAVA 代码实现(不推荐在大输入使用):

public class SqrtSubtractOdd { // O(√x) 仅示意public int mySqrt(int x) {int count = 0;             // 已经减去的奇数个数int odd = 1;               // 当前奇数while (x >= odd) {         // 还能继续减x -= odd;              // 减去当前奇数odd += 2;              // 下一个奇数count++;               // 次数加 1}return count;              // count 即平方根整数部分}
}

思路五:线性扫描 / 暴力枚举(对比:最坏 O(√x) 甚至 O(x))

原理与适用场景:
从 0/1 开始逐个 i 检查 i^2 <= x。若 i^2 > x 则 i-1 为答案。显然最坏 O(√x),大数性能差。仅用于展示与二分/牛顿的差距。

实现步骤:

  1. i=0 while ( (long)(i+1)^2 <= x ) i++。
  2. 返回 i。

(代码略)


补充说明(对比分析)

  1. 复杂度对比:

    • 二分:时间 O(log x),空间 O(1)。
    • 牛顿:时间 O(log log x) 级迭代(实际常数极小),空间 O(1)。
    • 位试商:时间 O(log x)(固定常数循环),空间 O(1)。
    • 减奇数:时间 O(√x),空间 O(1)。
    • 暴力:时间 O(√x)(若从 0 到 √x),空间 O(1)。
  2. 精度与正确性:牛顿、二分、位试商都能得到精确整数部分;需要注意牛顿法结束条件,用 while(y*y > x) 是常用且安全的整数版写法。

  3. 溢出风险:

    • midmid、candidatecandidate 使用 long。
    • 牛顿中 y, x / y 均用 long。
  4. 推荐优先级:

    • 若强调模板清晰:二分。
    • 若追求极少迭代:牛顿。
    • 若对位运算感兴趣或限制除法:位试商。
  5. 测试用例建议:

    • x=0 -> 0
    • x=1 -> 1
    • x=4 -> 2
    • x=8 -> 2
    • x=15 -> 3
    • x=2147395599 (46339^2) -> 46339
    • x=2147483647 (INT_MAX) -> 46340
  6. 常见错误:

    • 忘记 long 导致乘法溢出得到负数。
    • 牛顿法用 double 造成精度向上取整,需最后强制向下取整。
    • 二分上界使用 x/2 时未处理 x=2,3 等小值(已在特判覆盖)。
  7. 总结:

    • 本题最易维护方案:二分。
    • 高效方案:牛顿(写法更短)。
    • 特殊环境:位运算试商法。

综上,推荐掌握至少二分与牛顿两种,以便在不同语境下快速实现整数平方根。

http://www.dtcms.com/a/362866.html

相关文章:

  • 【算法】哈希表专题
  • 单元测试总结2
  • 【大前端】Vue 和 React 主要区别
  • dy图文批量下载
  • 【C++】模板(初阶)--- 初步认识模板
  • 从一行 var a = 1 开始,深入理解 V8 引擎的心脏
  • 【Linux我做主】进程退出和终止详解
  • 掌握设计模式--模板方法模式
  • 前缀树约束大语言模型解码
  • Ollama:本地大语言模型部署和使用详解
  • 【论文阅读】DeepSeek-LV2:用于高级多模态理解的专家混合视觉语言模型
  • ObjectMapper一个对象转json串为啥设计成注入?...
  • 【学Python自动化】 7. Python 输入与输出学习笔记
  • Pandas Python数据处理库:高效处理Excel/CSV数据,支持分组统计与Matplotlib可视化联动
  • 车载刷写架构 --- ECU软件更新怎么保证数据的正确性?
  • Ansible 循环、过滤器与判断逻辑
  • 【保姆级喂饭教程】把chrome谷歌浏览器中的插件导出为CRX安装包
  • Android init 实战项目
  • 文件页的预取逻辑
  • IAM(Identity and Access Management)
  • windows中使用cmd/powershell查杀进程
  • k8s的CRD自定义资源类型示例
  • 从全球视角到K8s落地的Apache IoTDB实战
  • 2025年新版C语言 模电数电及51单片机Proteus嵌入式开发入门实战系统学习,一整套全齐了再也不用东拼西凑
  • AI零售创业公司:零眸智能
  • Elasticsearch 深分页限制与解决方案
  • Flink RuntimeContext和FunctionContext:状态计算的核心桥梁
  • flink中的窗口的介绍
  • uni-app iOS 应用版本迭代与上架实践 持续更新的高效流程
  • Windows远程连接:SSH+RDP+Server