题目描述:
你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。
给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。
Level | AC rate | Solutions |
Easy | 45.6% | 3 |
441. 排列硬币 - 力扣(LeetCode)https://leetcode.cn/problems/arranging-coins/
题目解析1:
首先一看到这个题,我需要找到一个数为x,从1到x的数字之和要小于等于n,而从1到x+1的数字之和要大于n,我们最最直接就想到了穷举的方法。我们从第一行开始做累减,直到我们所剩的硬币不能够布满第x+1行也就是小于等于x时,我们返回x即可,代码如下:
class Solution {
public int arrangeCoins(int n) {
int x = n;
for(int i = 1;i<=n;i++)
{
x = x-i;
if(x<=i)return i;
}
return -1;
}
}
时间复杂度为O(N),空间复杂度为O(1),又因为在我们的for循环中,若没有返回值,则表明行数i一定小于我们目前所剩的硬币数,所以我们不用使用x来表示所剩的硬币数,直接在循环中使用x来进行表示,代码如下:
class Solution {
public int arrangeCoins(int n) {
for(int i = 1;i<=n;i++){
n = n-i;
if(n<=i)return i;
}
return -1;
}
}
执行用时:9 ms, 在所有 Java 提交中击败了13.18%的用户
内存消耗:39.1 MB, 在所有 Java 提交中击败了19.09%的用户
题目解析2:
在暴力穷举的算法基础上,我们考虑到,n枚硬币最多只能放n行(在n=1的极限情况下),在其余情况下,n枚硬币所放置的行数一定是小于n的,所以我们的1和n之间进行二分查找,找到一个值x,1到x的所有数字之和小于等于n,1到x+1的所有数字之和大于n即可,代码如下:
class Solution {
public int arrangeCoins(int n) {
int low = 0;
int high = n;
while(low<=high)
{
int mid = (low+high)/2;
if((mid+1)*mid/2==n)return mid;
else if((mid+1)*mid/2>n){
high = mid-1;
}
else{
low = mid+1;
}
}
return high;
}
}
但这出现了时间超时的情况,在经过一番调整过后,我们将第8、9两行的if判断条件由除法变为乘法后,即可通过,代码如下所示:
class Solution {
public int arrangeCoins(int n) {
int low = 0;
int high = n;
while(low<=high){
int mid = (low+high)/2;
if((long)(mid+1)*mid==(long)2*n)return mid;
else if((long)(mid+1)*mid>(long)2*n){
high = mid-1;
}
else{
low = mid+1;
}
}
return high;
}
}
执行用时:1 ms, 在所有 Java 提交中击败了95.55%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了26.54%的用户
Ps: 首先我们在网上进行了相关资料的查找,发现在代码运行效率上,移位>乘法>除法,这可能是导致我们两个程序运行差异的原因。随后我们又将代码改为移位运算来进行了比较,发现位运算并不能对我们的代码进行改善,随后我对(mid+)*mid的值进行输出,发现出现了负值,应该是超出了int的数值范围,最后将类型转换为long进行了验证,发现运行成功了,并且以位运算进行实现,降低了一定内存消耗。
最后在二分查找的基础上替代使用牛顿迭代法进行查找,实现更低的时间复杂度。首先我们对牛顿迭代法进行简单的介绍:根据切线是曲线的线性逼近进行求根,大致的做法就是我们在曲线上一点做关于该曲线的切线,该切线与x轴的交点做y轴的平行线,将该平行线与曲线的交点作为新点重复操作来对根值进行逼近,详细的证明见:(15条消息) 如何通俗易懂地讲解牛顿迭代法?_马同学图解数学的博客-CSDN博客_newton迭代法https://blog.csdn.net/ccnt_2012/article/details/81837154因为在本问题中,我们要求得的值x需要满足1到x所有数之和等于硬币总数n,即(x+1)*x/2=n,通过化简得到x^2 = 2n-x。根据牛顿迭代法求平方根得到x(n+1) = (x(n)+sqrt(2n-x(n)/x(n))/2,代码如下:
class Solution {
public int arrangeCoins(int n) {
int ans = (int)sqrt(n,n);
if((long)(ans+2)*(ans+1)/2<=n)return ans+1;
else return ans;
}
double sqrt(double x,int num){
double res = (x+((long)num*2-x)/x)/2;
if(Math.abs(res-x)<1e-12)return res;
else{
return sqrt(res,num);
}
}
}
执行用时:1 ms, 在所有 Java 提交中击败了95.55%的用户
内存消耗:38.8 MB, 在所有 Java 提交中击败了55.86%的用户
在这里不做过多的介绍了,因为牛顿迭代法对于根值的逼近,有可能因为double类型数值范围的原因,导致逼近不是单调的,会出现得到的值比预期结果偏小一位的情况,目前还没有对这个问题有一个比较好的解决办法,使用到的办法就是判断该值加1的情况是否合理,如果合理就返回该值加1作为答案,若不合理就返回该值即可。