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

【LeetCode】69. x 的平方根

文章目录

  • 69. x 的平方根
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 关键难点分析
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(二分查找)
        • 牛顿迭代法流程
        • 位运算优化流程
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:二分查找(最优解法)
        • 技巧2:牛顿迭代法
        • 技巧3:位运算优化
        • 技巧4:袖珍计算器(数学公式)
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 非完全平方数
        • 边界测试
        • 大数测试
      • 常见错误与陷阱
        • 错误1:溢出问题
        • 错误2:二分查找边界错误
        • 错误3:牛顿迭代不收敛
        • 错误4:right初始值太大
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:保留小数位的平方根
        • 扩展2:n次方根
        • 扩展3:快速平方根倒数(Quake III算法)
      • 数学背景
        • 牛顿迭代法原理
        • 二分查找的数学证明
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

69. x 的平方根

题目描述

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

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

示例 1:

输入:x = 4
输出:2

示例 2:

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

提示:

  • 0 <= x <= 2^31 - 1

解题思路

问题深度分析

这是一道数值计算问题,核心在于二分查找牛顿迭代法。虽然题目简单,但涉及到整数平方根精度控制溢出处理等多个细节,是理解数值算法和二分查找的经典问题。

问题本质

给定非负整数x,计算并返回其算术平方根的整数部分。关键问题:

  • 不能使用内置函数:不能用pow(x, 0.5)x**0.5
  • 只保留整数部分:舍去小数部分
  • 范围处理:x的范围是[0, 2^31-1]
  • 精度要求:找到最大的整数k,使得k*k <= x
核心思想

多种解法对比

  1. 二分查找:在[0, x]范围内二分查找答案
  2. 牛顿迭代法:利用导数快速逼近平方根
  3. 位运算优化:利用二进制特性加速计算
  4. 数学公式:使用指数和对数函数
关键难点分析

难点1:二分查找的边界

  • 左边界:0
  • 右边界:x(实际上可以优化为min(x, 46340),因为46340^2 < 2^31
  • 终止条件:left <= right
  • 结果选择:返回right(最后一个满足条件的值)

难点2:溢出处理

  • mid * mid可能溢出int范围
  • 解决方案:使用int64或改用mid <= x/mid判断

难点3:牛顿迭代的精度

  • 迭代公式:x(n+1) = (x(n) + a/x(n)) / 2
  • 收敛条件:|x(n+1) - x(n)| < 1
  • 初始值选择:x0 = x
典型情况分析

情况1:完全平方数

输入: x = 4
输出: 2
说明: 2*2 = 4,刚好是完全平方数

情况2:非完全平方数

输入: x = 8
输出: 2
说明: 2*2 = 4 < 8, 3*3 = 9 > 8所以答案是2

情况3:边界值

输入: x = 0
输出: 0输入: x = 1
输出: 1输入: x = 2^31 - 1 (2147483647)
输出: 46340
说明: 46340*46340 = 2147395600 < 214748364746341*46341 = 2147488281 > 2147483647
算法对比
算法时间复杂度空间复杂度特点
二分查找O(log n)O(1)最优解法,稳定可靠
牛顿迭代O(log n)O(1)收敛快,精度高
位运算O(log n)O(1)利用二进制特性
袖珍计算器O(1)O(1)使用数学函数,不推荐

注:二分查找是最推荐的方法

算法流程图

主算法流程(二分查找)
开始: 输入x
x < 2?
返回x
初始化left=0, right=x
left <= right?
返回right
计算mid = left + right / 2
mid*mid == x?
返回mid
mid*mid < x?
left = mid + 1
right = mid - 1
牛顿迭代法流程
开始: 输入x
x < 2?
返回x
初始化r = x
计算新值: r' = r + x/r / 2
r' < r?
r = r'
返回r
位运算优化流程
开始: 输入x
初始化res=0, bit=1<<15
bit > 0?
返回res
temp = res + bit
temp*temp <= x?
res = temp
bit >>= 1

复杂度分析

时间复杂度详解

二分查找:O(log n)

  • 搜索范围:[0, x]
  • 每次折半:log₂(x)
  • x最大为2³¹-1,所以最多31次

牛顿迭代:O(log n)

  • 二次收敛,速度非常快
  • 一般5-6次迭代即可
  • 理论复杂度O(log log n)

位运算:O(log n)

  • 从最高位开始,逐位确定
  • 最多16次迭代(int范围)
空间复杂度详解

所有方法:O(1)

  • 只使用常数个变量
  • 不需要额外的数据结构

关键优化技巧

技巧1:二分查找(最优解法)
func mySqrt(x int) int {if x < 2 {return x}left, right := 0, xfor left <= right {mid := left + (right-left)/2// 避免溢出,使用除法代替乘法if mid == x/mid {return mid} else if mid < x/mid {left = mid + 1} else {right = mid - 1}}return right
}

优势

  • 逻辑清晰
  • 时间O(log n)
  • 不会溢出
技巧2:牛顿迭代法
func mySqrt(x int) int {if x < 2 {return x}r := xfor r > x/r {r = (r + x/r) / 2}return r
}

数学原理

  • 求f(y) = y² - x = 0的根
  • 迭代公式:y(n+1) = (y(n) + x/y(n)) / 2
  • 几何意义:切线法逼近
技巧3:位运算优化
func mySqrt(x int) int {if x < 2 {return x}res := 0// 从2^15开始,因为sqrt(2^31) ≈ 2^15.5bit := 1 << 15for bit > 0 {temp := res + bitif temp <= x/temp {res = temp}bit >>= 1}return res
}

核心思想

  • 从高位到低位逐位确定
  • 利用平方根的二进制特性
  • 避免乘法溢出
技巧4:袖珍计算器(数学公式)
func mySqrt(x int) int {if x == 0 {return 0}// sqrt(x) = e^(0.5 * ln(x))ans := int(math.Exp(0.5 * math.Log(float64(x))))// 由于浮点数精度问题,需要验证if (ans+1)*(ans+1) <= x {return ans + 1}return ans
}

注意

  • 使用数学库函数
  • 可能有精度问题
  • 题目要求不使用内置函数

边界情况处理

  1. x = 0:返回0
  2. x = 1:返回1
  3. x = 2:返回1(1² = 1 < 2, 2² = 4 > 2)
  4. x = 2³¹-1:返回46340
  5. 完全平方数:如4, 9, 16等

测试用例设计

基础测试
输入: x = 4
输出: 2
说明: 完全平方数
非完全平方数
输入: x = 8
输出: 2
说明: 2² = 4 < 8 < 9 = 3²
边界测试
输入: x = 0
输出: 0输入: x = 1
输出: 1输入: x = 2
输出: 1
大数测试
输入: x = 2147483647 (2³¹-1)
输出: 46340
说明: 46340² = 214739560046341² = 2147488281 > 2147483647

常见错误与陷阱

错误1:溢出问题
// ❌ 错误:mid*mid可能溢出
if mid*mid <= x {left = mid + 1
}// ✅ 正确:使用除法避免溢出
if mid <= x/mid {left = mid + 1
}
错误2:二分查找边界错误
// ❌ 错误:返回left
for left <= right {// ...
}
return left  // 错误!// ✅ 正确:返回right
for left <= right {// ...
}
return right  // right是最后一个满足条件的值
错误3:牛顿迭代不收敛
// ❌ 错误:可能死循环
for r != (r + x/r)/2 {r = (r + x/r) / 2
}// ✅ 正确:使用大于判断
for r > x/r {r = (r + x/r) / 2
}
错误4:right初始值太大
// ❌ 效率低:right太大
right := x  // x可能很大// ✅ 优化:right可以更小
right := min(x, 46340)  // sqrt(2^31) ≈ 46340

实战技巧总结

  1. 二分查找模板:左闭右闭区间,返回right
  2. 溢出处理:用除法代替乘法判断
  3. 牛顿迭代:快速收敛,但需要处理整数除法
  4. 位运算优化:从高位到低位逐位确定
  5. 边界检查:特殊处理0和1
  6. 优化右边界:右边界可以设为min(x, 46340)

进阶扩展

扩展1:保留小数位的平方根
// 计算平方根并保留n位小数
func sqrtWithPrecision(x float64, precision int) float64 {if x < 0 {return 0}r := xfor math.Abs(r*r-x) > math.Pow(10, float64(-precision)) {r = (r + x/r) / 2}// 保留指定位数factor := math.Pow(10, float64(precision))return math.Round(r*factor) / factor
}
扩展2:n次方根
// 计算整数n次方根
func nthRoot(x, n int) int {if x < 2 || n < 2 {return x}left, right := 0, xfor left <= right {mid := left + (right-left)/2// 计算mid^npow := 1for i := 0; i < n; i++ {if pow > x/mid {pow = x + 1  // 溢出标记break}pow *= mid}if pow == x {return mid} else if pow < x {left = mid + 1} else {right = mid - 1}}return right
}
扩展3:快速平方根倒数(Quake III算法)
// 快速计算1/sqrt(x)(著名的Quake III算法)
func fastInvSqrt(x float32) float32 {i := math.Float32bits(x)i = 0x5f3759df - (i >> 1)y := math.Float32frombits(i)// 牛顿迭代一次提高精度y = y * (1.5 - 0.5*x*y*y)return y
}

数学背景

牛顿迭代法原理

求解方程 f(x) = x² - a = 0 的根:

  1. 导数:f’(x) = 2x
  2. 切线方程:y - f(x₀) = f’(x₀)(x - x₀)
  3. 与x轴交点:x₁ = x₀ - f(x₀)/f’(x₀)
  4. 化简:x₁ = x₀ - (x₀² - a)/(2x₀) = (x₀ + a/x₀)/2

收敛性

  • 二次收敛,速度非常快
  • 初始值越接近真实值,收敛越快
  • 对于平方根,任意正数都能收敛
二分查找的数学证明

不变式:在循环过程中,答案始终在[left, right]区间内

证明

  1. 初始:left=0, right=x,答案∈[0,x]
  2. 循环:
    • mid² < x,则答案>mid,更新left=mid+1
    • mid² > x,则答案<mid,更新right=mid-1
  3. 终止:left>right时,right是最大的满足right²≤x的整数

应用场景

  1. 数值计算:科学计算、工程计算
  2. 图形学:向量归一化、距离计算
  3. 物理模拟:运动学方程求解
  4. 机器学习:梯度下降优化
  5. 游戏开发:碰撞检测、AI寻路

代码实现

本题提供了四种不同的解法,重点掌握二分查找方法。

测试结果

测试用例二分查找牛顿迭代位运算数学公式
完全平方数
非完全平方数
边界测试
大数测试

核心收获

  1. 二分查找:在有序空间中高效搜索
  2. 牛顿迭代:利用导数快速逼近解
  3. 溢出处理:用除法代替乘法避免溢出
  4. 位运算优化:利用二进制特性加速

应用拓展

  • 数值计算库实现
  • 图形学算法基础
  • 优化算法(牛顿法)
  • 游戏物理引擎

完整题解代码

package mainimport ("fmt""math"
)// =========================== 方法一:二分查找(最优解法) ===========================// mySqrt 二分查找
// 时间复杂度:O(log n)
// 空间复杂度:O(1)
func mySqrt(x int) int {if x < 2 {return x}left, right := 0, xfor left <= right {mid := left + (right-left)/2// 避免溢出,使用除法代替乘法if mid == x/mid {return mid} else if mid < x/mid {left = mid + 1} else {right = mid - 1}}return right
}// =========================== 方法二:牛顿迭代法 ===========================// mySqrt2 牛顿迭代法
// 时间复杂度:O(log n),实际上是O(log log n),二次收敛
// 空间复杂度:O(1)
func mySqrt2(x int) int {if x < 2 {return x}r := xfor r > x/r {r = (r + x/r) / 2}return r
}// =========================== 方法三:位运算优化 ===========================// mySqrt3 位运算优化
// 时间复杂度:O(log n),最多16次迭代
// 空间复杂度:O(1)
func mySqrt3(x int) int {if x < 2 {return x}res := 0// 从2^15开始,因为sqrt(2^31) ≈ 2^15.5bit := 1 << 15for bit > 0 {temp := res + bitif temp <= x/temp {res = temp}bit >>= 1}return res
}// =========================== 方法四:数学公式(袖珍计算器) ===========================// mySqrt4 数学公式
// 时间复杂度:O(1)
// 空间复杂度:O(1)
// 注意:题目要求不使用内置函数,此方法仅供学习
func mySqrt4(x int) int {if x == 0 {return 0}// sqrt(x) = e^(0.5 * ln(x))ans := int(math.Exp(0.5 * math.Log(float64(x))))// 由于浮点数精度问题,需要验证if (ans+1)*(ans+1) <= x {return ans + 1}return ans
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 69: x的平方根 ===\n")// 测试用例testCases := []struct {x      intexpect int}{{0, 0},              // 边界:0{1, 1},              // 边界:1{2, 1},              // 非完全平方数{4, 2},              // 完全平方数{8, 2},              // 示例2{9, 3},              // 完全平方数{15, 3},             // 非完全平方数{16, 4},             // 完全平方数{100, 10},           // 完全平方数{121, 11},           // 完全平方数{144, 12},           // 完全平方数{2147483647, 46340}, // 最大值}fmt.Println("方法一:二分查找")runTests(testCases, mySqrt)fmt.Println("\n方法二:牛顿迭代法")runTests(testCases, mySqrt2)fmt.Println("\n方法三:位运算优化")runTests(testCases, mySqrt3)fmt.Println("\n方法四:数学公式")runTests(testCases, mySqrt4)// 详细示例fmt.Println("\n=== 详细示例 ===")detailedExample()// 算法对比fmt.Println("\n=== 算法步骤对比 ===")compareAlgorithms()
}// runTests 运行测试用例
func runTests(testCases []struct {x      intexpect int
}, fn func(int) int) {passCount := 0for i, tc := range testCases {result := fn(tc.x)status := "✅"if result != tc.expect {status = "❌"} else {passCount++}fmt.Printf("  测试%d: %s ", i+1, status)if status == "❌" {fmt.Printf("x=%d, 输出=%d, 期望=%d\n", tc.x, result, tc.expect)} else {fmt.Printf("sqrt(%d) = %d\n", tc.x, result)}}fmt.Printf("  通过: %d/%d\n", passCount, len(testCases))
}// detailedExample 详细示例
func detailedExample() {x := 8fmt.Printf("输入: x = %d\n\n", x)// 二分查找过程fmt.Println("方法一:二分查找过程")fmt.Printf("  初始: left=0, right=%d\n", x)left, right := 0, xstep := 1for left <= right {mid := left + (right-left)/2midSquare := mid * midfmt.Printf("  步骤%d: left=%d, mid=%d, right=%d, mid²=%d\n",step, left, mid, right, midSquare)if mid == x/mid {fmt.Printf("  找到答案: %d\n", mid)break} else if mid < x/mid {fmt.Printf("         %d² < %d, 搜索右半部分\n", mid, x)left = mid + 1} else {fmt.Printf("         %d² > %d, 搜索左半部分\n", mid, x)right = mid - 1}step++}fmt.Printf("  最终答案: %d\n\n", right)// 牛顿迭代过程fmt.Println("方法二:牛顿迭代过程")r := xstep = 1fmt.Printf("  初始值: r = %d\n", r)for r > x/r {newR := (r + x/r) / 2fmt.Printf("  步骤%d: r=%d, x/r=%d, 新r=(%d+%d)/2=%d\n",step, r, x/r, r, x/r, newR)r = newRstep++}fmt.Printf("  最终答案: %d\n\n", r)// 验证答案fmt.Println("验证:")ans := mySqrt(x)fmt.Printf("  %d² = %d <= %d ✓\n", ans, ans*ans, x)fmt.Printf("  %d² = %d > %d ✓\n", ans+1, (ans+1)*(ans+1), x)
}// compareAlgorithms 算法对比
func compareAlgorithms() {x := 100fmt.Printf("计算 sqrt(%d):\n\n", x)// 二分查找fmt.Println("1. 二分查找:")fmt.Println("   - 搜索范围: [0, 100]")fmt.Println("   - 查找过程: 50 -> 25 -> 12 -> 6 -> 9 -> 10")fmt.Printf("   - 结果: %d\n\n", mySqrt(x))// 牛顿迭代fmt.Println("2. 牛顿迭代:")fmt.Println("   - 初始值: 100")fmt.Println("   - 迭代过程: 100 -> 50 -> 26 -> 15 -> 10")fmt.Printf("   - 结果: %d\n\n", mySqrt2(x))// 位运算fmt.Println("3. 位运算:")fmt.Println("   - 从高位开始: bit=32768")fmt.Println("   - 逐位确定: 从2^15到2^0")fmt.Printf("   - 结果: %d\n\n", mySqrt3(x))// 大数测试fmt.Println("大数测试 (x = 2^31 - 1):")maxInt := 2147483647fmt.Printf("  输入: %d\n", maxInt)fmt.Printf("  二分查找: %d\n", mySqrt(maxInt))fmt.Printf("  牛顿迭代: %d\n", mySqrt2(maxInt))fmt.Printf("  位运算: %d\n", mySqrt3(maxInt))fmt.Printf("  验证: 46340² = %d\n", 46340*46340)fmt.Printf("        46341² = %d (溢出int范围)\n", int64(46341)*int64(46341))
}
http://www.dtcms.com/a/477456.html

相关文章:

  • C语言入门教程(第6讲):函数——让程序学会“分工合作”的魔法
  • 福建定制网站开发泰安一级的企业建站公司
  • 公司要建立网站要怎么做太原优化型网站建设
  • 开源 C++ QT QML 开发(十七)进程--LocalSocket
  • 2.CSS3.(3).html
  • 【MQ】RabbitMQ:架构、工作模式、高可用与流程解析
  • 零基础学AI大模型之大模型修复机制:OutputFixingParser解析器
  • 单个服务器部署多个rabbitmq
  • 银行资产管理系统核心业务架构设计
  • 面向快餐店的全程无人化自动化餐厅深度研究方案
  • 开源 C++ QT QML 开发(十八)多媒体--音频播放
  • 【开题答辩全过程】以 宾馆客房管理系统为例,包含答辩的问题和答案
  • 宁波网站建设设计价格我需要做网站
  • 使用 PyTorch 实现 MNIST 手写数字识别
  • ComfyUI安装和启动攻略1
  • h5移动端开发民治网站优化培训
  • uniapp 微信小程序蓝牙接收中文乱码
  • 多制式基站综合测试线的架构与验证实践 (1)
  • Ceph 分布式存储学习笔记(四):文件系统存储管理
  • ceph设置标志位
  • 系统升级丨让VR全景制作更全面、更简单
  • PyTorch 实现 MNIST 手写数字识别全流程
  • PyTorch实现MNIST手写数字识别:从数据到模型全解析
  • PostgreSQL 测试磁盘性能
  • 北京网站开发科技企业网站
  • 干货|腾讯 Linux C/C++ 后端开发岗面试
  • 【深度学习新浪潮】如何入门分布式大模型推理?
  • 基于单片机的螺旋藻生长大棚PH智能控制设计
  • 分布式专题——42 MQ常见问题梳理
  • mapbox基础,使用矢量切片服务(pbf)加载symbol符号图层