大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。
题目
1753. 移除石子的最大得分
你正在玩一个单人游戏,面前放置着大小分别为 a、b 和 c 的 三堆 石子。
每回合你都要从两个 不同的非空堆 中取出一颗石子,并在得分上加 1 分。当存在 两个或更多 的空堆时,游戏停止。
给你三个整数 a 、b 和 c ,返回可以得到的 最大分数 。
示例 1:
输入: a = 2, b = 4, c = 6
输出: 6
解释: 石子起始状态是 (2, 4, 6) ,最优的一组操作是:
- 从第一和第三堆取,石子状态现在是 (1, 4, 5)
- 从第一和第三堆取,石子状态现在是 (0, 4, 4)
- 从第二和第三堆取,石子状态现在是 (0, 3, 3)
- 从第二和第三堆取,石子状态现在是 (0, 2, 2)
- 从第二和第三堆取,石子状态现在是 (0, 1, 1)
- 从第二和第三堆取,石子状态现在是 (0, 0, 0)
总分:6 分 。
示例 2:
输入: a = 4, b = 4, c = 6
输出: 7
解释: 石子起始状态是 (4, 4, 6) ,最优的一组操作是:
- 从第一和第二堆取,石子状态现在是 (3, 3, 6)
- 从第一和第三堆取,石子状态现在是 (2, 3, 5)
- 从第一和第三堆取,石子状态现在是 (1, 3, 4)
- 从第一和第三堆取,石子状态现在是 (0, 3, 3)
- 从第二和第三堆取,石子状态现在是 (0, 2, 2)
- 从第二和第三堆取,石子状态现在是 (0, 1, 1)
- 从第二和第三堆取,石子状态现在是 (0, 0, 0)
总分:7 分 。
示例 3:
输入: a = 1, b = 8, c = 8
输出: 8
解释: 最优的一组操作是连续从第二和第三堆取 8 回合,直到将它们取空。
注意,由于第二和第三堆已经空了,游戏结束,不能继续从第一堆中取石子。
提示:
1 <= a, b, c <= 105
思路
- 这种消除石头的题目,一般比较常用的解法是贪心。什么是贪心呢? 就是你能用大的值去消除就别用小的值;
- 我们可以先给三个元素直接排个序,然后先判断一下边界条件, 即前两个数相加小于第三个数,那么直接返回前两个数之和即可;
- 我们拿最大值直接减去最小值,算出需要保留多少个元素,这时候中间的值可能有剩余也可能没有,但是不管,我们直接减完差值之后,会发现原本的最小值变成了现在的最大值了。然后再进行同样的操作,直至有两个元素为
0即可。
实现
/**
* @param {number} a
* @param {number} b
* @param {number} c
* @return {number}
*/
var maximumScore = function(a, b, c) {
let result = 0;
let arr = [a, b, c].sort((a, b) => a - b);
// 两个加起来都不够,直接消除
if (arr[0] + arr[1] <= arr[2]) {
return arr[0] + arr[1];
}
while (arr.length > 1) {
// 最大值等于1的时候,说明是最后一次消除了
if (arr[arr.length - 1] === 1) {
return result + 1;
}
// 每次消除掉最大值-最小值的差
let temp = arr[arr.length - 1] - arr[0];
// 当最大值和最小值相等了,最后一次消除
if (temp === 0) {
// 如果三个数都相等, 后两个数各分出一半来先消除第一个数,然后它两再自相残杀
// 这样子它两剩余部分都是各自的一小半
if (arr.length === 3) {
result += Math.floor(arr[0] / 2);
}
return result + arr[0];
}
// 结果累加
result += temp;
// 最后两个元素执行消除
arr[arr.length - 1] -= temp;
arr[arr.length - 2] -= temp;
// 去除等于0的值
arr = arr.filter(v => v > 0);
// 原本的最小值变成了现在的最大值
arr.length > 1 && arr.push(arr.shift());
}
return result;
};
第一版的代码,思路已经实现了,但是由于写了不少冗余代码,看起来不够直观,于是对代码做一个精简和优化。
代码优化
/**
* @param {number} a
* @param {number} b
* @param {number} c
* @return {number}
*/
var maximumScore = function(a, b, c) {
let result = 0;
let arr = [a, b, c].sort((a, b) => a - b);
// 两个加起来都不够,直接消除
if (arr[0] + arr[1] <= arr[2]) {
return arr[0] + arr[1];
}
while (arr.length === 3) {
// 每次消除掉最大值-最小值的差
let temp = arr[arr.length - 1] - arr[0];
// 当最大值和最小值相等了,最后一次消除
if (temp === 0) {
return result + arr[0] + Math.floor(arr[0] / 2);
}
// 结果累加
result += temp;
// 最后两个元素执行消除
arr[arr.length - 1] -= temp;
arr[arr.length - 2] -= temp;
// 如果只剩下两位数, 直接返回
if (arr[arr.length - 2] === 0) {
return result +arr[arr.length - 1];
}
// 原本的最小值变成了现在的最大值
arr.push(arr.shift());
}
return result;
};
结果
找规律
当然,这道题目也可以用数学中的找规律方法做消除,我们会发现在前两数之和大于第三个数的时候,最终和是奇数就只会消除剩下一个,是偶数则刚好全部消除,所以我们直接拿所有值向下取整即可。
数学规律代码
/**
* @param {number} a
* @param {number} b
* @param {number} c
* @return {number}
*/
var maximumScore = function(a, b, c) {
let arr = [a, b, c].sort((a, b) => a - b);
// 两个加起来都不够,直接消除
if (arr[0] + arr[1] <= arr[2]) {
return arr[0] + arr[1];
}
return Math.floor((arr[0] + arr[1] + arr[2]) / 2);
};
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。