算是Leetcode上求平方根的延伸版:
Leetcode 69. x的平方根
原始的 Sqrt(x)
描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
原题有两种主要解法:
二分法
class Solution:
def mySqrt(self, x: int) -> int:
if x == 0 or x == 1:
return x
if x == 2:
return 1
i = 0
j = x
for idx in range(x):
h = int((i+j)/2)
r = h**2
l = (h-1)**2
if l >x:
j = h
if r < x:
i = h
if r == x:
return int(h)
if l<=x and r>x:
return int(h-1)
牛顿法
class Solution:
def mySqrt(self, x: int) -> int:
if x<=1:
return x
cur = x/2
thresh = 1e-5
while True:
pre = cur
cur = (cur + x / cur) / 2
if abs(cur - pre) < thresh:
return int(cur)
所要实现的 log(x, base)
描述:
给定一个可允许的误差 error
给定
log
b
x
\log_bx
logbx中的base和x
求在误差error范围内的log函数
思考:
那么不难看出,方法也大致符合上面两种方法,也用二分法和牛顿法就可以了,但是其实还有更多的方法来做,如泰勒级数和算术几何平均近似,这里可以科学上网参考wiki 传送门
其中一种思路:
设
log
b
x
=
g
(
x
)
\log_bx=g(x)
logbx=g(x)
那么实际上要求:寻找一个
y
y
y,使得
g
(
x
)
−
y
≤
error
g(x)-y\le\text{error}
g(x)−y≤error
那么由
log
b
x
=
g
(
x
)
\log_bx=g(x)
logbx=g(x) 可得
b
g
(
x
)
=
x
b^{g(x)}=x
bg(x)=x
也就是说寻找一个
y
y
y,使得
∣
b
y
−
x
∣
<
error
| b^y-x|<\text{error}
∣by−x∣<error
下面是两种主要解法:
二分法
最暴力的解法了,不过这个需要根据log函数的实际情况设置好初始最小值和最大值,避免幂运算的时候超出最大表示范围:
def log(x, base, error):
"""Need set minv maxv"""
if x < 0:
raise ValueError('X can not less than 0')
if base < 0 or base == 1:
raise ValueError('base can not less than 0 or equal 1')
if x == 1:
return 0
minv = -1e4
maxv = 10
while True:
mid = (minv + maxv) / 2
r = base ** mid
if abs(x - r) <= error:
return mid
if r > x:
maxv = mid
if r <= x:
minv = mid
牛顿法
这里的牛顿法就要好很多了,不用给出最大值最小值的范围
原目标是寻找一个
y
y
y,使得
∣
b
y
−
x
∣
<
error
| b^y-x|<\text{error}
∣by−x∣<error
那么实际上可以认为我们需要求解方程:
f
(
y
)
=
b
y
−
x
=
0
f(y)=b^y-x=0
f(y)=by−x=0
根据牛顿法的公式:
x
n
+
1
=
x
n
−
f
(
x
)
f
′
(
x
)
x_{n+1}=x_n - \frac{f(x)}{f'(x)}
xn+1=xn−f′(x)f(x)
代入我们设定的符号,可得:
y
n
+
1
=
y
n
+
f
(
y
)
f
′
(
y
)
y_{n+1} = y_n + \frac{f(y)}{f'(y)}
yn+1=yn+f′(y)f(y)
即:
y
n
+
1
=
y
n
+
b
y
n
−
x
b
y
n
⋅
ln
b
y_{n+1} = y_n + \frac{b^{y_n}-x}{b^{y_n}\cdot \ln b}
yn+1=yn+byn⋅lnbbyn−x
实际上还面临着
ln
b
\ln b
lnb的求解问题,这时为了节省运算时间我们让
ln
b
=
1
\ln b=1
lnb=1
或者也可以采用
a
n
+
1
=
a
n
+
exp
(
a
n
)
−
b
exp
(
a
n
)
a_{n+1} = a_n + \frac{\exp(a_n)-b}{\exp(a_n)}
an+1=an+exp(an)exp(an)−b 的方式得出一个大致的
ln
b
\ln b
lnb,如误差在1e-2即可。
"""牛顿法1"""
def log(x, base, error):
if x < 0:
raise ValueError('X can not less than 0')
if base < 0 or base == 1:
raise ValueError('base can not less than 0 or equal 1')
if x == 1:
return 0
y = 1
while True:
y_ = y
y = y + (x - base ** y) / (base ** y)
if abs(y - y_) < error:
return y
对于
ln
x
\ln x
lnx的求法,wiki上给出了对
exp
(
y
/
2
)
−
x
exp
(
y
−
2
)
=
0
\exp(y/2)-x\exp(y-2)=0
exp(y/2)−xexp(y−2)=0进行优化,即求:
y
n
+
1
=
y
n
+
2
⋅
x
−
exp
(
y
n
)
x
+
exp
(
y
n
)
y_{n+1} = y_n + 2\cdot\frac{x-\exp(y_n)}{x+\exp(y_n)}
yn+1=yn+2⋅x+exp(yn)x−exp(yn)
仿照这个式子,下面有:
"""牛顿法2"""
def log(x, base, error):
if x < 0:
raise ValueError('X can not less than 0')
if base < 0 or base == 1:
raise ValueError('X can not less than 0')
if x == 1:
return 0
y = 1
while True:
y_ = y
y = y + 2 * (x - base ** y) / (x + base ** y)
if abs(y - y_) < error:
return y
比较
log(x=3, base=6, error=1e-5)
0.6131471927654585 # math.log 系统给出的方法
0.6131471623666584 # 二分法 error = 3.039880003274931e-08 loop=31
0.6131431996226185 # 牛顿法1 error = 4.269444931814803e-06 loop=44
0.6131514622103903 # 牛顿法2 error = 3.99314283994201e-06 loop=47
在base
更大的时候,循环次数loop
差距会更大,但是几种方法的优劣差距不变
其他
求解 log b x \log_bx logbx还是要比 ln x \ln x lnx麻烦很多的,二分法会更省事,其次求解log在java里面采用位运算的方法,具体如何实现的可以考虑看下源码,这里不做过多讲解了。另外关于 ln x \ln x lnx的求法可以参考wiki,利用类似于泰勒级数的方法求得:
ln ( x + 1 ) = x − x 2 2 + x 3 3 − . . . \ln(x+1)= x- \frac{x^2}{2}+\frac{x^3}{3}-... ln(x+1)=x−2x2+3x3−...
或者 ln ( 1 + 1 n ) = 2 ( − 1 2 n + 1 + 1 3 ( 2 n + 1 ) 3 + 1 5 ( 2 n + 1 ) 5 . . . ) \ln(1+\frac{1}{n})= 2(-\frac{1}{2n+1}+\frac{1}{3(2n+1)^3}+\frac{1}{5(2n+1)^5}...) ln(1+n1)=2(−2n+11+3(2n+1)31+5(2n+1)51...)