排列硬币
你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行可能是不完整的。
给你一个数字 n ,计算并返回可形成完整阶梯行的总行数。
注意: 1 < = n < = 2 31 − 1 1 <= n <= 2^{31}-1 1<=n<=231−1
示例:
输入:n = 5
输出:2
解释:因为第三行不完整,所以返回 2 。
因为排列硬币时,最后一行是可以不完整的,所以我们在有限的硬币数量内尽可能地占有更多行。
假设有 x x x 行,那么 x x x 行一共有 x ( x + 1 ) 2 \frac {x(x+1)} {2} 2x(x+1) 个硬币,所以占有的硬币数要尽可能接近提供的硬币数 n n n 且不能大于 n n n。
解法一:二分查找
因为 x x x 的取值范围较大,所以我们可以用二分查找来提高效率。二分查找的思想也很简单,所以直接上代码:
代码如下:
class Solution:
def arrangeCoins(self, n: int) -> int:
left, right = 1, n
while left < right:
mid = (left + right + 1) // 2
if mid * (mid + 1) <= 2 * n:
left = mid
else:
right = mid - 1
return left
这里有个小细节需要注意,就是在求mid
时语句为:
mid = (left + right +1) // 2
这里+1
的原因:普通二分在查找时,若不能整除,则取左边或者右边都可以,但是在这个排列硬币的题目中,我们需要让值尽量靠右些,因为我们要找硬币数最接近
n
n
n 的结果。考虑如下情况,比如left=1,right=2,中间点为1.5,普通二分中取1或者2皆可,但往往是取1,这道题是要逼近右边界,考虑循环退出条件,如果取1的话,就永远无法退出循环了。
解法二:数学公式
因为我们知道
x
x
x 层硬币的总数为
x
(
x
+
1
)
2
\frac {x(x+1)} {2}
2x(x+1) ,那么我们不妨直接令:
x
(
x
+
1
)
2
=
n
\frac {x(x+1)} {2}=n
2x(x+1)=n
对上述方程进行求解,显然最终求得的结果就为 ⌊ x ⌋ \lfloor{x}\rfloor ⌊x⌋
解得: x 1 = − 1 + 8 n + 1 2 x 2 = − 1 − 8 n + 1 2 x_{1}=\frac {-1+\sqrt{8n+1}} {2} x_{2}=\frac {-1-\sqrt{8n+1}} {2} x1=2−1+8n+1x2=2−1−8n+1
显然舍去 x 2 x_2 x2 ,那么结果就为 ⌊ x 1 ⌋ \lfloor{x_1}\rfloor ⌊x1⌋
代码如下:
class Solution:
def arrangeCoins(self, n: int) -> int:
return int((pow(8 * n + 1, 0.5) - 1) / 2)
解法三:位运算
一开始看到这个题解还以为是什么新奇的做法,其实本质上还是循环枚举每一种可能性,但是因为 1 < = n < = 2 31 − 1 1 <= n <= 2^{31}-1 1<=n<=231−1,所以使用位运算可以增加效率。
首先,我们用ans
来表示最终得结果,初始时ans = 0
;
因为我们要不断循环,所以我们需要额外一个变量来对ans
进行修改。因为答案最长也不会超过16位(
I
n
t
.
M
A
X
=
16
\sqrt{Int.MAX}=16
Int.MAX=16)
不妨令这个变量为mask
,初始时,mask = 100000000000000
,每次循环时将1进行右移。
在每轮循环时,首先将ans
和mask
按位或,如果按位或之后ans*(ans+1)/2 > n
,代表当前结果已经超过了所给硬币数,那么就将ans
和mask
按位异或,即将ans
恢复到本轮循环没开始得状态,继续下一轮循环。
代码如下:
class Solution(object):
def arrangeCoins(self, n):
"""
:type n: int
:rtype: int
"""
ans = 0
mask = 1 << 15
while mask > 0:
ans |= mask
if ans*(ans+1)/2>n:
ans ^= mask
mask >>= 1
return ans