算法之美
标签(空格分隔): Leetcode 算法 时间复杂度
过完年刚回实验室,回归状态之余,随便在Leetcode上选了道简单的算法题目,accept虽然容易,但是如果考虑到时间复杂度的话,还是值得小酌一二,在这里与诸君分享。
题目选自Leetcode第441题, Arrange coins。题目概述如下,给你n枚硬币,你要堆积起k层,使得每一层的硬币数量为k枚,给出的例子如下:
Example 1:
*n = 5
The coins can form the following rows:
¤
¤ ¤
¤ ¤
Because the 3rd row is incomplete, we return 2.*
Example 2:
*n = 8
The coins can form the following rows:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
Because the 4th row is incomplete, we return 3.*
咋看之下,一般人都能想到很直白的解决方案,那就是从第一层开始逐层累加,并判断当前层是否满足要求,硬币是否用尽,代码如下:
class Solution(object):
def arrangeCoins(self, n):
"""
:type n: int
:rtype: int
"""
# This is a stupid solution
k = 0
total = 0
while n - total > k:
k += 1
total += k
return k
一直需要累加到
1+2+⋯+k+k+1>n
1
+
2
+
⋯
+
k
+
k
+
1
>
n
,返回的k值即为所求。显然可知该朴素方法(Naive Algorithm)的算法时间复杂度为
O(n−−√)
O
(
n
)
那么有没有一种更快的方法呢?答案是有的,我们可以使用二分法来加快搜索 k 的过程,每一次取中间值 mid 来判断,当
1+2+⋯+mid<=n
1
+
2
+
⋯
+
m
i
d
<=
n
且
1+2+⋯+mid+(mid+1)>n
1
+
2
+
⋯
+
m
i
d
+
(
m
i
d
+
1
)
>
n
时,我们的 mid 即为所求层数 k ,代码如下所示:
class Solution(object):
def arrangeCoins(self, n):
"""
:type n: int
:rtype: int
"""
# The solution is faster than the original one, but, can we make it faster? absolutely we can
def helper(low, high, n):
if low > high:
return 0
mid = (low + high) / 2
if (1 + mid) * mid / 2 <=n and (2 + mid) * (1 + mid) / 2 > n:
return mid
elif (2 + mid) * (1 + mid) / 2 <= n:
return helper(mid, high, n)
else:
return helper(low, mid, n)
return helper(1,n,n)
显然该方法的时间复杂度为
O(log(n))
O
(
l
o
g
(
n
)
)
,相比于前面的朴素算法,这算是个不错的解法,那么,还能不能更快一些?
这时候,数学的魅力就展现出来了。
1+2+⋯+k<=n
1
+
2
+
⋯
+
k
<=
n
,求和之后的表达式是一个一元二次不等式
k2+k−2n<=0
k
2
+
k
−
2
n
<=
0
,显然在等号处可以取最大k值,因此可直接解出
k=−1+8n−1√2
k
=
−
1
+
8
n
−
1
2
(k 为正),代码只有一行,时间复杂度为
O(1)
O
(
1
)
:
return ((8*n-1)**0.5-1)/2
一叶知秋,同样的问题,不同的解法,差异之大,令人咋舌。这也正是算法吸引人的魅力所在。随着人工智能热潮的席卷,很多问题都可以靠AI来解决,但是真正能取得突破性进展,仍然依赖于人类开发的算法的优劣好坏。这也算是刷题过程中的一点小小的感悟吧,与君共勉。