二分查找题目:x 的平方根
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 预备知识
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:x 的平方根
出处:69. x 的平方根
难度
3 级
题目描述
要求
给定一个非负整数 x \texttt{x} x,计算并返回 x \texttt{x} x 的算术平方根。
由于返回类型是整数,因此小数部分被舍去,只返回结果的整数部分。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) \texttt{pow(x, 0.5)} pow(x, 0.5) 或者 x ** 0.5 \texttt{x ** 0.5} x ** 0.5。
示例
示例 1:
输入: x = 4 \texttt{x = 4} x = 4
输出: 2 \texttt{2} 2
示例 2:
输入: x = 8 \texttt{x = 8} x = 8
输出: 2 \texttt{2} 2
解释: 8 \texttt{8} 8 的算术平方根是 2.82842... \texttt{2.82842...} 2.82842...,由于小数部分被舍去,因此返回 2 \texttt{2} 2。
数据范围
- 0 ≤ x ≤ 2 31 − 1 \texttt{0} \le \texttt{x} \le \texttt{2}^\texttt{31} - \texttt{1} 0≤x≤231−1
解法一
思路和算法
用 r r r 表示 x x x 的算术平方根的整数部分,则 r r r 是非负整数。
当 x = 0 x = 0 x=0 时, r = 0 r = 0 r=0。当 x > 0 x > 0 x>0 时,对于任意非负整数 y y y,当 y ≤ r y \le r y≤r 时 y 2 ≤ x y^2 \le x y2≤x,当 y > r y > r y>r 时 y 2 > x y^2 > x y2>x。因此可以使用二分查找得到 r r r。
由于 r r r 一定在范围 [ 0 , x ] [0, x] [0,x] 内,因此初始时二分查找的范围的下界和上界是 low = 0 \textit{low} = 0 low=0, high = x \textit{high} = x high=x。
每次查找时,取 mid \textit{mid} mid 为 low \textit{low} low 和 high \textit{high} high 的平均数向上取整,比较 mid 2 \textit{mid}^2 mid2 和 x x x 的大小关系,调整二分查找的范围。
-
如果 mid 2 ≤ x \textit{mid}^2 \le x mid2≤x,则 r r r 大于等于 mid \textit{mid} mid,因此在范围 [ mid , high ] [\textit{mid}, \textit{high}] [mid,high] 中继续查找。
-
如果 mid 2 > x \textit{mid}^2 > x mid2>x,则 r r r 小于 mid \textit{mid} mid,因此在范围 [ low , mid − 1 ] [\textit{low}, \textit{mid} - 1] [low,mid−1] 中继续查找。
当 low = high \textit{low} = \textit{high} low=high 时,结束查找,此时 low \textit{low} low 即为 r r r,返回 low \textit{low} low。
实现方面需要注意处理溢出的情况。
二分查找的过程中,每次取 mid = ⌈ low + high 2 ⌉ \textit{mid} = \Big\lceil \dfrac{\textit{low} + \textit{high}}{2} \Big\rceil mid=⌈2low+high⌉,等价于 mid = low + ⌊ high − low + 1 2 ⌋ \textit{mid} = \textit{low} + \Big\lfloor \dfrac{\textit{high} - \textit{low} + 1}{2} \Big\rfloor mid=low+⌊2high−low+1⌋。由于 x x x 的取值范围是 [ 0 , 2 31 − 1 ] [0, 2^{31} - 1] [0,231−1],因此 high \textit{high} high 的最大值可能是 2 31 − 1 2^{31} - 1 231−1。当 low = 0 \textit{low} = 0 low=0 且 high = 2 31 − 1 \textit{high} = 2^{31} - 1 high=231−1 时, high − low + 1 = 2 31 \textit{high} - \textit{low} + 1 = 2^{31} high−low+1=231,超出了 32 32 32 位整型的表示范围,会导致溢出。
为了避免溢出,一种做法是将 low \textit{low} low、 high \textit{high} high、 mid \textit{mid} mid 都声明成 64 64 64 位整型,另一种做法是对 x = 0 x = 0 x=0 的情况特判。具体做法是,当 x = 0 x = 0 x=0 时返回 0 0 0,当 x > 0 x > 0 x>0 时 x x x 的算术平方根一定大于等于 1 1 1,因此二分查找的范围的下界和上界初始化为 low = 1 \textit{low} = 1 low=1, high = x \textit{high} = x high=x,可以避免溢出。
代码
class Solution {public int mySqrt(int x) {if (x == 0) {return 0;}int low = 1, high = x;while (low < high) {int mid = low + (high - low + 1) / 2;if ((long) mid * mid <= x) {low = mid;} else {high = mid - 1;}}return low;}
}
复杂度分析
-
时间复杂度: O ( log x ) O(\log x) O(logx)。二分查找的次数是 O ( log x ) O(\log x) O(logx),每次查找的时间是 O ( 1 ) O(1) O(1),时间复杂度是 O ( log x ) O(\log x) O(logx)。
-
空间复杂度: O ( 1 ) O(1) O(1)。
解法二
预备知识
该解法使用牛顿迭代法计算给定非负整数的算术平方根。牛顿迭代法(Newton’s method)又称为牛顿-拉夫逊(拉弗森)方法(Newton-Raphson method),是牛顿提出的一种在实数域和复数域上近似求解方程的方法。
牛顿迭代法的本质是使用泰勒级数,从初始近似值开始快速向函数的零点逼近。其原理如下。
假设函数 f ( x ) f(x) f(x) 的零点是 r r r,则 r r r 是方程 f ( x ) = 0 f(x) = 0 f(x)=0 的根。选取 x 0 x_0 x0 作为 r r r 的初始近似值,则 ( x 0 , f ( x 0 ) ) (x_0, f(x_0)) (x0,f(x0)) 是函数 y = f ( x ) y = f(x) y=f(x) 的图像上的一点,过该点作函数图像的切线,切线方程是 y − f ( x 0 ) = f ′ ( x 0 ) ( x − x 0 ) y - f(x_0) = f'(x_0)(x - x_0) y−f(x0)=f′(x0)(x−x0),切线与 x x x 轴交点的横坐标是 x 1 = x 0 − f ( x 0 ) f ′ ( x 0 ) x_1 = x_0 - \dfrac{f(x_0)}{f'(x_0)} x1=x0−f′(x0)f(x0), x 1 x_1 x1 称为 r r r 的一次近似值。使用相同的方法,从 x 1 x_1 x1 计算得到 x 2 = x 1 − f ( x 1 ) f ′ ( x 1 ) x_2 = x_1 - \dfrac{f(x_1)}{f'(x_1)} x2=x1−f′(x1)f(x1), x 2 x_2 x2 称为 r r r 的二次近似值。推广到一般情形,当 k > 0 k > 0 k>0 时, x k = x k − 1 − f ( x k − 1 ) f ′ ( x k − 1 ) x_k = x_{k - 1} - \dfrac{f(x_{k - 1})}{f'(x_{k - 1})} xk=xk−1−f′(xk−1)f(xk−1), x k x_k xk 称为 r r r 的 k k k 次近似值,上述公式称为牛顿迭代公式。
如果函数的零点存在,则近似值会向零点收敛。当迭代次数达到预设的上限,或者相邻两次近似值足够接近时,即可结束迭代过程。
思路和算法
计算 x x x 的算术平方根,等价于计算函数 f ( t ) = t 2 − x ( t ≥ 0 ) f(t) = t^2 - x (t \ge 0) f(t)=t2−x(t≥0) 的零点。该函数的导函数为 f ′ ( t ) = 2 t f'(t) = 2t f′(t)=2t,牛顿迭代公式为 t k = 1 2 ( t k − 1 + x t k − 1 ) t_k = \dfrac{1}{2}\Big( t_{k - 1} + \dfrac{x}{t_{k - 1}} \Big) tk=21(tk−1+tk−1x)。
将 x x x 作为零点的初始近似值,由于当 x x x 未到达零点时, x x x 大于 x \sqrt{x} x,因此每次迭代时都会将 t k t_k tk 减小,可以到达 x \sqrt{x} x 处的零点。
牛顿迭代公式中,自变量 t t t 出现在分母上。当 x = 0 x = 0 x=0 时, t t t 的初始近似值 t 0 = 0 t_0 = 0 t0=0,此时会出现被零除的情况。为了避免出现被零除的情况,应确保 t > 0 t > 0 t>0,因此当 x = 0 x = 0 x=0 时返回 0 0 0,当 x > 0 x > 0 x>0 时使用牛顿迭代法计算 x x x 的算术平方根。
代码
class Solution {static final double EPSILON = 1e-6;public int mySqrt(int x) {if (x == 0) {return 0;}boolean flag = true;double sqrt = x;while (flag) {double curr = (sqrt + x / sqrt) / 2;if (Math.abs(curr - sqrt) < EPSILON) {flag = false;} else {sqrt = curr;}}return (int) sqrt;}
}
复杂度分析
-
时间复杂度: O ( log x ) O(\log x) O(logx)。牛顿迭代法计算算术平方根的时间复杂度是 O ( log x ) O(\log x) O(logx)。
-
空间复杂度: O ( 1 ) O(1) O(1)。