记录 1 道算法题
移除石子的最大得分
要求:
* 从三堆石头里面选择两堆,然后从这两堆里面分别拿走一个石头,然后计一分。
* 当石头出现两堆或以上,数量为 0 的时候停止。
* 返回最大总分
暴力解法
经过观察发现,只要我们每次取三堆石头里面的最大值和最小值,就可以得到最大的分数。分数说白了就是能够取多少次。
function maximumScore(a, b, c) {
let arr = [a, b, c]
let count = 0
// 每次判断数组是不是还有 2 个以上。0 会被剔除
while ((arr = arr.filter(_ => _ > 0)).length >= 2) {
let i = arr[0], ii
// 判断如果数组每一个值都一样,就默认扣前两个
// 之所以有这一层是因为 Math.min 和 Math.max 会得到相同的下标
if (arr.filter(_ => _ === i).length === arr.length) {
i = 0
ii = 1
} else {
// 最大值 最小值
i = arr.indexOf(Math.min(...arr))
ii = arr.indexOf(Math.max(...arr))
}
// 计数,计分
arr[i]--
arr[ii]--
count++
}
return count
}
接下来是优化的解法
我们又发现了一个规律似乎将石子堆升序排列之后,前两个堆的和跟结果很相近。
例如: 2 4 6 --> 6, 1 8 8 --> 8, 1 1 1 --> 1
似乎是前两个堆的和如果小于等于第三个堆,就直接返回前两个堆的和。如果前两个堆的和大于第三个堆就是前两个堆的和 - 1。
于是就尝试了一下,结果不完全对,遇到这样一个测试用例, 24 19 24 --> 33。好像大于的时候不是单纯的 - 1。
但是前两个和小于等于第三个时的规律是正确的。问题在于大于第三个的时候的规律是什么。
于是去看了看题解,原来是只要把大于第三个的情况转成小于等于的情况就可以了。
假设有 a, b, c 三个石头堆,升序排列,已知 a + b <= c 时,结果是 a + b。
当 a + b > c 的时候,a + b - c 就是前两个和第三个相比,超出来的部分。如果我们让 a - n, b - n, 最终使 (a-n) + (b-n) <= c。那么这种情况下,结果就是前两个的和,当然还要加上 n。
还是用上面例子, 19 24 24
19 + 24 - 24 = 19,超出了19个,假如我们要在前两个里面减去 19 个石子。最好的办法就是平均分配,大家都减去 19 / 2。就是取前面两个堆拿出 19 / 2 次石子,加 19 / 2 分。
于是就变成了这样 10 15 24, 但是好像 10 + 15 多出了一个,再取一次吧。
于是最后 9 14 24,符合小于等于规律,结果是 9 + 14, 我们再加上之前取的 19 / 2 次和 1 次。
答案就是 9 + 14 + 9 + 1 = 33。
我们会发现当多出的部分是奇数的时候,我们需要多取一次,所以其实是 Math.ceil(19 / 2)。
完整代码如下:
function maximumScore(a, b, c) {
let arr = [a, b, c]
// 用的冒泡排序
for (let i = 0; i < arr.length - 1; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
}
// a + b
const count = arr[0] + arr[1]
// 前两个和 与 第三个的关系
if (count > arr[2]) {
// 超出了的要分的次数 k
const k = Math.ceil((count - arr[2]) / 2)
// 前面 (a-k) + (b-k) + k 的简化式子
return count - k
} else {
// 小于等于直接返回
return count
}
}
结束