[前端]_一起刷leetcode 1753. 移除石子的最大得分

422 阅读5分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

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

思路

  1. 这种消除石头的题目,一般比较常用的解法是贪心。什么是贪心呢? 就是你能用大的值去消除就别用小的值;
  2. 我们可以先给三个元素直接排个序,然后先判断一下边界条件, 即前两个数相加小于第三个数,那么直接返回前两个数之和即可;
  3. 我们拿最大值直接减去最小值,算出需要保留多少个元素,这时候中间的值可能有剩余也可能没有,但是不管,我们直接减完差值之后,会发现原本的最小值变成了现在的最大值了。然后再进行同样的操作,直至有两个元素为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;
};

结果

image.png

找规律

当然,这道题目也可以用数学中的找规律方法做消除,我们会发现在前两数之和大于第三个数的时候,最终和是奇数就只会消除剩下一个,是偶数则刚好全部消除,所以我们直接拿所有值向下取整即可。

数学规律代码

/**
 * @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);
};

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。