给你一个非负整数 x(0 <= x <= 231 - 1) ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
背景:多数方程不存在求根公式,因此求精确根非常困难,甚至不可解,从而寻找方程的近似根就显得特别重要。牛顿迭代法是求方程根的重要方法之一
可证明,在无法直接求出 f ( x ) f(x) f(x)的根 b b b的时候,可通过不断迭代得到无限接近与 b b b的根 x n x_n xn:
lim n → ∞ x n = b {{\lim_{n\to ∞}}}x_n = b n→∞limxn=b
具体过程:
1、确定 f ( x ) f(x) f(x):这里设为 f ( x ) = x 2 − a f(x) = x^2 -a f(x)=x2−a
2、确定精度 ϵ \epsilon ϵ
3、目的:求解使得 f ( x ) = x 2 − a = 0 f(x) = x^2 -a = 0 f(x)=x2−a=0的第一个满足精度的近似解 x n x_n xn
4、迭代条件:
5、迭代关系式: x n = x n − 1 − f ( x n − 1 ) f ′ ( x n − 1 ) x_{n} = x_{n-1} - {f(x_{n-1})\over f'(x_{n-1})} xn=xn−1−f′(xn−1)f(xn−1)
空间复杂度 O ( 1 ) O(1) O(1)
不溢出的原因在于,我们结合了平方根的具体题目:
next_x = 0.5 * (cur_x + a/cur_x)
,否则必然要计算 f f f,即 x 2 x^2 x2;该解法我令a = -x,是为了表示,牛顿迭代初始x的选取,会决定你最后得到平方根x的正负号,如果题目规定返回正数,则最后我们还是要加个绝对值省点心哈。
执行用时:40 ms, 在所有 Python3 提交中击败了79.28% 的用户
内存消耗:14.9 MB, 在所有 Python3 提交中击败了61.01% 的用户
class Solution:
def mySqrt(self, x: int) -> int:
# 单独处理x =0,防止后面0作为分母被除
if x == 0:
return 0
a, cur_x, epsilon = x, -x, 1e-7
while True: # 精度满足则break
# 直接用化简公式则不用计算cur_x的平方,防止溢出
next_x = 0.5 * (cur_x + a/cur_x)
# 这里精度用的是x之间的精度差,而不是f函数值的精度差,用f还要计算平方,会溢出
if abs(next_x - cur_x) < epsilon:
break
cur_x = next_x
return int(abs(cur_x))
注意点:
class Solution:
def mySqrt(self, x: int) -> int:
# 单独处理x =0,防止后面0作为分母被除
if x == 0:
return 0
def f(x):
return x ** 2 - a
def df(x):
return 2 * x
a, cur_x, epsilon = x, -x, 1e-7
while True: # 精度满足则break
next_x = cur_x - f(cur_x) / df(cur_x)
# 这里精度用的是x之间的精度差,而不是f函数值的精度差,用f还要计算平方,会溢出
if abs(next_x - cur_x) < epsilon:
break
cur_x = next_x
return float(format(abs(cur_x), "0.3f"))
>>> mySqrt(8)
2.828
这里采用f之间的精度差,没采用while True写法,避免了不单独考虑x=0,分母会除0的情况。
执行用时:36 ms, 在所有 Python3 提交中击败了91.88% 的用户
内存消耗:14.9 MB, 在所有 Python3 提交中击败了58.91% 的用户
class Solution:
def mySqrt(self, x: int) -> int:
a = x # 改成求a的平方根,近似值用x估计:f(x) = x^2 - a
def f(x):
return x ** 2 - a
def df(x):
return 2 * x
epsilon = 10 ** (-4) # 自己设置个精度,本文其实0就可以
cur_x = a # 初始x_0,随意选取f = x0^2 - a > epsilon即可
# 牛顿迭代法
while abs(f(cur_x)) - 0 > epsilon: # 不取等号
next_x = cur_x - f(cur_x) / df(cur_x)
cur_x = next_x
return int(cur_x) # 本文要求取整
把找a的平方根问题,转化为:在[0, …, a]这一有序整数数组中,搜索能使得 x 2 ≤ a x^2 \leq a x2≤a的最大元素。
注意点:
if mid <= a / mid
形式,mid就会=0mid = left + (right - left) // 2
if mid <= a / mid
,mid^2可能溢出if mid <= a / mid
: 必须取等号,否则都不符合搜索本身条件了时间复杂度 O ( log N ) O(\log N) O(logN):二分搜索次数
空间复杂度 O ( 1 ) O(1) O(1)
执行用时:40 ms, 在所有 Python3 提交中击败了79.28% 的用户
内存消耗:14.8 MB, 在所有 Python3 提交中击败了92.07% 的用户
class Solution:
def mySqrt(self, x: int) -> int:
# 1、主要针对x=0如果left从0开始搜索会出现mid=0作为分母的情况
if x <= 1: return x
# 2、二分查找:在 [1, a]搜索使得x^2 <= a 的最大x
a = x
left, right = 1, a
while left <= right:
mid = left + (right - left) // 2
if mid <= a / mid: # m^2 <= a 可能溢出;必须取等号
left = mid + 1 # 当前mid符合条件,试图找更大的x
else:
right = mid - 1 # 当前mid不符,则搜索更小的x
return right # 举例可知right最终指向result
class Solution:
def mySqrt(self, x: int) -> int:
if x <= 1: return x
# return self.newton(x)
return self. binary_search(x)
def newton(self, a):
cur_x, epsilon = a, 1e-7
while True:
next_x = 0.5 * (cur_x + a / cur_x)
if abs(cur_x - next_x) <= epsilon:
break
cur_x = next_x
return abs(int(cur_x))
def binary_search(self, a):
# 在[2, a]寻找最大的使得 x^2 <= a的x
left, right = 2, a
while left <= right:
mid = left + (right - left) // 2
if mid <= a / mid: # 必须取等号
left = mid + 1
else:
right = mid - 1
return right