各位题友大家好,@负雪明烛 又出现了。
题意
第 i 层可以放置 i 个硬币,将 n 个硬币按行排列,计算并返回可形成 完整阶梯行 的总行数。
解题思路
解法一:模拟法
一看题目是 Easy,用暴力的模拟法应该能通过。
定义了两个变量:
- level 表示遍历到了第 level 层,同时这层可以放 level 个硬币。
- count 表示累加了 level 层之后,总的有多少硬币。
因为最后一行可能不满,所以当 count + level + 1 <= n
的时候,才累加上这一层的硬币数。其中 level + 1
表示下一层的硬币数目。
注意:count + level + 1
有可能会超过 int 最大值,所以把 count 定义为了 long 型。
代码如下:
class Solution {
public:
int arrangeCoins(int n) {
long level = 0; // 当前层
long count = 0; // 加上 level 层之后,总的有多少硬币
while (count + level + 1 <= n) { // level + 1 表示下一层的数目
level ++; // 得到下一层的数目
count += level; // 累加下一层的数目
}
return level;
}
};
- 时间复杂度:$O(n)$
- 空间复杂度:$O(1)$
解法二:二分查找
每层可以容纳的硬币数量分别是 1, 2, 3, 4, ..., i
,根据等差数列的计算公式,可以求得当第 i 行铺满的时候,第 0 ~ i
行总的硬币数目有 (i + 1) * i / 2
。
那么可以使用二分查找,快速的查找出 (i + 1) * i / 2 <= n
的最大数字 $i$ ,即为所求。
二分查找的模板可以见 AlgoWiki:https://ojeveryday.github.io/AlgoWiki/#/BinarySearch/README
下面的代码中,二分查找定义的区间是 [left, right]
双闭区间。只要符合条件 (mid + 1) * mid / 2 <= n
,就让 left = mid + 1
,所以会导致 left
多计算一行,因此,最终 left
需要减一。
注意:mid * (mid + 1) / 2
有可能会非常大,所以我把 mid 的类型设置为了 long long.
class Solution {
public:
int arrangeCoins(int n) {
int left = 0;
int right = n; // [left, right]
while (left <= right) {
long long mid = left + (right - left) / 2;
if (mid * (mid + 1) / 2 <= n) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left - 1;
}
};
- 时间复杂度:$O(log(n))$
- 空间复杂度:$O(1)$
方法三:数学公式
根据二分查找部分的分析,我们其实就是求满足 (i + 1) * i / 2 = n
这个方程,通过求解一元二次方程就能得到 i = (-1 + sqrt(8 * n + 1)) / 2
,此时 i
是个浮点数,向下取整就得到了完整阶梯行。
class Solution {
public:
int arrangeCoins(int n) {
return (int)((-1 + sqrt(1 + 8 * (long) n)) / 2);
}
};
- 时间复杂度:$O(1)$
-
刷题心得
模拟法最需要注意的是每个变量的准确含义:比如 count 是累加了 level 后,还是累加了 level 前?
- 二分查找的难点在于把本题抽象成为一个查找问题:需要数学公式的抽象。
- 数学公式的难点是:公式求解,以及变量类型。
OK,以上就是 @负雪明烛 写的今天题解的全部内容了,如果你觉得有帮助的话,求赞、求关注、求收藏。如果有疑问的话,请在下面评论,我会及时解答。
关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。
祝大家 AC 多多,Offer 多多!我们下期再见!