:-: 4.3 排列硬币
*****
**题干:**
你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。给定一个数字 n,找出可形成完整阶梯行的总行数。
n 是一个非负整数,并且在32位有符号整型的范围内。
示例 1:
```
n = 5
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤
因为第三行不完整,所以返回2.
```
示例 2:
```
n = 8
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
因为第四行不完整,所以返回3.
```
**题目分析:**
由题目分析得我们返回的值是不完整的那一行的上一行的行数,且每行的硬币个数是当前的行数,且符合首项为1,公差为1的等差数列(a1 = 1,d = 1),等差数列前n项和公式为Sn = n * a1 + (((n * (n - 1)) / 2) * d。
**新手有可能遇到的解题思路陷阱:**
返回值返回了当前不完整的行数中的硬币数,当正好排满时返回了当前的行数的上一行的行数,应该是下一行行数。
**解题思路分析以及代码实现:**
第一种思路:暴力破解:1.用当前硬币数减去从第一行至不完整行数的上一行的硬币数,每次成功减去则进行计数器累加,当硬币数小于当前行数,则输出计数器的值;2.根据等差数列求和公式,依次计算前n项和与输入的硬币数比较,当前n项和不大于输入的硬币数则计数器累加,当大于则返回计数器的值。
**为什么中间使用long类型?因为中间值的计算可能超过int类型所能表达的范围。以下同上。**
**为什么 i 的取值范围小于Math.sqrt(sum * 2)?因为Sn = (n^2 + n) / 2,但是n不能确定,所以sum = n^2 / 2代替。或者直接将 i <= Math.sqrt(sum * 2)的限制条件去除掉(for (int i = 1; ; i++))**
第一种思路代码一:
```
public int arrangeCoins(int n) {
long sum = n;
int result = 0;
for (int i = 1; i <= Math.sqrt(sum * 2); i++) {
if (n < i) {
break;
}
n = n - i;
result++;
}
return result;
}
```
第一种思路代码二:
```
public static int arrangeCoins(int n) {
long sum = n;
int result = 0;
for (long i = 1; i <= Math.sqrt(sum * 2); i++) {
if (((i * i + i) / 2) > n) {
return result;
}
result++;
}
return result;
}
```
**复杂度分析:**
时间复杂度:O(n)。
空间复杂度:O(1)。
第二种思路:二分法,找出前i行之和刚好大于n的临界点值,怎么找出这个值?还是使用等差数列前n项和公式。
**为什么使用low + (high - low) / 2而不使用(high + low) / 2呢?
防止溢出!解释:看下面两个问题的答案就好了,解释的很清楚。**
[low+(high-low)/2 is cheaper than (low+high)/2 ](https://stackoverflow.com/questions/38395275/lowhigh-low-2-is-cheaper-than-lowhigh-2)
[Why in Java (high + low) / 2 is wrong but (high + low) >>> 1 is not?](https://stackoverflow.com/questions/13785210/why-in-java-high-low-2-is-wrong-but-high-low-1-is-not)
**为什么(low + high) >>> 1会有一个这样的替代呢?为什么不是(low + high) >> 1它呢?
因为 >>> 无符号,>> 有符号!中文版解释:看下面这个问题的答案就好了,解释的很清楚。**
[为什么在 Java 中用 (low+high)>>>1 代替 (low+high)/2 或 (low+high)>>1 来计算平均值呢?](https://www.zhihu.com/question/20101702)
第二种思路代码:
```
public int arrangeCoins(int n) {
if (n <= 1)
return n;
long low = 0, high = n;
while (low <= high) {
/* long mid = (low + high) >>> 1; */
long mid = low + (high - low) / 2;
if ((mid * (mid + 1)) >>> 1 <= n)
low = mid + 1;
else
high = mid - 1;
}
return (int) (low - 1);
}
```
**复杂度分析:**
时间复杂度:O(n)。
空间复杂度:O(1)。
第三种思路:二元一次方程求解。a * x^2 + b * x + c = 0;
:-: ![](https://box.kancloud.cn/ca350b0b8b74c2c35fb71084b103a997_226x56.png)或者![](https://box.kancloud.cn/19350907cb33a96e4f15cf64f3022c37_226x59.png)
上述问题首项为1,公差为1的等差数列(a1 = 1,d = 1),等差数列前n项和公式为Sn = n * a1 + (((n * (n - 1)) / 2) * d可化简为Sn = (n^2 + n) / 2,我们把输入的硬币数当作常数c,则代入以上方程求解(求前n项和是当前输入硬币数的n(有可能不是整数)),且我们的数都是正数,所以我们的解也只要正数解,又因为返回的可能带有小数,我们又是获得不完整行数的上一行的行数,也就是说忽略掉小数,只保留正整数就可以了。
第三种思路代码:
```
public int arrangeCoins(int n) {
/* return (int) (Math.sqrt(1 + 8 * (long) n) - 1) / 2; */
return (int) (Math.sqrt(2 * (long) n + 0.25) - 0.5);
}
```
**复杂度分析:**
时间复杂度:O(1)。
空间复杂度:O(1)。
**若有理解错误的、写错的地方、更好的思路,方法,望各位读者评论或者发邮件指正或指点我,不胜感激。**
- 前言
- 第一部分 初级入门算法
- 第一章 数组
- 1.1 删除排序数组中的重复项
- 1.2 删除排序数组中的重复项 II
- 1.3 买卖股票的最佳时机
- 1.4 买卖股票的最佳时机 II
- 1.5 移动零
- 1.6 区间子数组个数
- 1.7 搜索插入位置
- 1.8 合并两个有序数组
- 1.9 两个数组的交集
- 第二章 哈希表
- 2.1 两数之和
- 2.2 错误的集合
- 2.3 翻转卡片游戏
- 2.4 有效的字母异位词
- 第三章 链表
- 第四章 数学
- 4.1 加一
- 4.2 反转整数
- 4.3 排列硬币
- 4.4 完全平方数
- 4.5 消除游戏
- 第五章 双指针
- 第六章 字符串
- 6.1 整数转罗马数字
- 6.2 罗马数字转整数
- 6.3 反转字符串
- 6.4 压缩字符串
- 6.5 验证回文串
- 6.6 长按键入
- 6.7 字符串中的第一个唯一字符
- 第七章 二分查找
- 7.1 猜数字大小
- 第八章 分治算法
- 第九章 动态规划
- 9.1 爬楼梯
- 9.2 使用最小花费爬楼梯
- 9.3 打家劫舍
- 9.4 打家劫舍 II
- 9.5 第N个泰波那契数
- 第十章 回溯算法
- 第十一章 栈
- 11.1 棒球比赛
- 第十二章 堆
- 12.1 数组中的第K个最大元素
- 第十三章 贪心算法
- 第十四章 排序
- 14.1 冒泡排序
- 14.2 鸡尾酒排序
- 14.3 选择排序
- 14.4 插入排序
- 14.5 折半插入排序
- 14.6 希尔排序
- 14.7 快速排序
- 14.8 树形选择排序
- 14.9 堆排序
- 第十五章 位运算
- 15.1 只出现一次的数字
- 第十六章 思维题
- 16.1 TinyURL 的加密与解密
- 16.2 灯泡开关
- 16.3 字母板上的路径
- 第十七章 队列
- 17.1 扑克牌顺序