第441题 排列硬币

题目描述:

你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。

给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。

LevelAC rateSolutions
Easy45.6%3

441. 排列硬币 - 力扣(LeetCode)icon-default.png?t=M666https://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迭代法icon-default.png?t=M666https://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作为答案,若不合理就返回该值即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值