一、题目描述:
leetcode441题:排列硬币
你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。
给定一个数字 n,找出可形成完整阶梯行的总行数。
n 是一个非负整数,并且在32位有符号整型的范围内。
示例 1:
n = 5
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤
因为第三行不完整,所以返回2.
示例 2:
n = 8
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
因为第四行不完整,所以返回3.
二、思路分析:
解法1:暴力循环
暴力循环是直观的解法,对n枚硬币进行循环遍历,解题思路为:
- 当n为1或者0时,返回n;
- 遍历累加记录sum,循环变量i为当前阶梯行数,当sum>n,所需要硬币数超过已有硬币,返回i-1。
复杂度分析:
- 时间复杂度:O(n),其中n为硬币枚数,最坏情况需要进行n次循环查找,时间复杂度为O(n)。
- 空间复杂度:O(1),未产生额外空间开销。
解法2:二分查找
由题意可知,形成0行阶梯一共需要0个硬币,形成1行阶梯一共需要1个硬币,形成2行阶梯一共需要3个硬币,形成x行阶梯一共需要 1+2+...+x = (1+x)x/2个硬币。
换句话说,形成0、1、2、3……n 行阶梯需要的硬币数是 [0, 1, 3, 6, ...., (1+n)n/2],可以看出是一个递增序列。现在题目问“有n个硬币时,能形成的完整阶梯行数”,抽象后就是“给你一个递增数组 nums,要求你返回 target 的位置,如果 target 不存在就返回距离 target 最近的左侧元素的位置”。
复杂度分析:
- 时间复杂度:O(log2n),其中n为硬币枚数,采用二分查找target的位置,最坏情况用O(log2n)完成搜索。
- 空间复杂度:O(1),未产生额外空间开销。
三、AC 代码:
解法1:暴力循环
var arrangeCoins = function(n) {
if(n <= 1){return n}
let sum = 0;
//遍历累加,如果超过n了,返回第a-1次
for(let i = 1;i<=n;i++){
sum += i;
if(sum>n) {
return i-1;
}
}
};
解法2:二分查找
var arrangeCoins = function(n) {
if (n == 0) {
return 0;
}
var left = 0;
// 只有 n 个硬币的情况下,最大肯定不会超过 n 行,所以这里把搜索的右侧界限定为 n
var right = n;
while (left <= right) {
var mid = left + ((right - left) >> 1);
// 形成 mid 行的阶梯一共需要 costToFinishMid 个硬币,这里是数学公式
var costToFinishMid = (1 + mid) * mid / 2;
if (costToFinishMid == n) {
return mid;
} else if (costToFinishMid < n) {
left = mid + 1;
} else if (costToFinishMid > n) {
right = mid - 1;
}
}
// 按照上述这种写法,right 在这里指向距离 target 最近的左侧元素的位置
return right;
};
四、总结:
当硬币数n较小时,暴力循环解法既简单,执行效率也不差;当n是较大的正整数,我们就需要使用二分查找来解决,减小时间复杂度。排列硬币,此问题有着单调递增的特点,适合采用二分查找来解决。
本文正在参与「掘金 3 月闯关活动」,点击查看活动详情