心算项目攻略:最大化卡牌得分技巧

题目

选自力扣
心算项目的挑战比赛中,要求选手从 N 张卡牌中选出 cnt 张卡牌,若这 cnt 张卡牌数字总和为偶数,则选手成绩「有效」且得分为 cnt 张卡牌数字总和。 给定数组 cards 和 cnt,其中 cards[i] 表示第 i 张卡牌上的数字。 请帮参赛选手计算最大的有效得分。若不存在获取有效得分的卡牌方案,则返回 0。

描述

示例 1:

输入:cards = [1,2,8,9], cnt = 3

输出:18

解释:选择数字为 1、8、9 的这三张卡牌,此时可获得最大的有效得分 1+8+9=18。

示例 2:

输入:cards = [3,3,1], cnt = 1

输出:0

解释:不存在获取有效得分的卡牌方案。

提示:

1 < = c n t < = c a r d s . l e n g t h < = 1 0 5 1 <= cnt <= cards.length <= 10^5 1<=cnt<=cards.length<=105
1 < = c a r d s [ i ] < = 1000 1 <= cards[i] <= 1000 1<=cards[i]<=1000

这段代码是一个Java类Solution中的方法 maxmiumScore,其目的是从给定的整数数组cards中选择cnt个元素,使得这cnt个元素的和最大,同时这个和必须是偶数。如果找不到这样的组合,那么返回的是在给定约束下的最大可能的偶数和。

代码

下面是对代码的逐行分析:

class Solution {
    public int maxmiumScore(int[] cards, int cnt) {
        Arrays.sort(cards); // 对数组进行排序,便于后续处理
        int ans = 0; // 初始化答案为0
        int tmp = 0; // 用于累加当前考虑的cnt个最大数的和
        int odd = -1, even = -1; // 分别记录当前cnt个数中最大的奇数和偶数
        int end = cards.length - cnt; // 确定需要遍历的数组的结束位置

        // 遍历数组,从大到小选择cnt个数
        for (int i = cards.length - 1; i >= end; i--) {
            tmp += cards[i]; // 累加当前数
            if ((cards[i] & 1) != 0) { // 判断是否为奇数
                odd = cards[i]; // 更新最大的奇数
            } else {
                even = cards[i]; // 更新最大的偶数
            }
        }

        // 如果累加的和本身就是偶数,直接返回
        if ((tmp & 1) == 0) {
            return tmp;
        }

        // 尝试替换一个奇数为一个更大的偶数
        for (int i = cards.length - cnt - 1; i >= 0; i--) {
            if ((cards[i] & 1) != 0) { // 找到数组中更大的奇数
                if (even != -1) { // 如果当前组合中有偶数
                    ans = Math.max(ans, tmp - even + cards[i]); // 尝试替换,更新最大和
                    break; // 找到一个合适的替换后,跳出循环
                }
            }
        }

        // 尝试替换一个偶数为一个更大的奇数
        for (int i = cards.length - cnt - 1; i >= 0; i--) {
            if ((cards[i] & 1) == 0) { // 找到数组中更大的偶数
                if (odd != -1) { // 如果当前组合中有奇数
                    ans = Math.max(ans, tmp - odd + cards[i]); // 尝试替换,更新最大和
                    break; // 找到一个合适的替换后,跳出循环
                }
            }
        }

        return ans; // 返回最大偶数和
    }
}

Arrays.sort(cards); 这行代码是Java中使用标准库函数对数组进行排序的示例。这里,Arrays.sort() 方法被用来对一个名为 cards 的数组进行排序。

逻辑原理

Arrays.sort() 方法的实现依赖于具体的Java版本和数组元素的类型。对于基本类型如 int, double 等,它使用了一种名为“双轴快速排序”的算法。对于对象数组,它使用了“TimSort”,这是一种由归并排序和插入排序结合而成的算法,特别适用于部分有序的数组,效率很高。

总结

Arrays.sort(cards); 这行代码会根据 cards 数组元素的自然顺序或实现的 Comparable 接口进行排序。如果 cards 数组的元素是自定义对象,那么这些对象必须实现 Comparable 接口,或者你可以提供一个 Comparator 来自定义排序逻辑。否则,Arrays.sort() 将无法正确排序对象数组。


在Java中,tmp & 1是一个位运算表达式,这里使用的是按位与运算符&。这个表达式主要用于判断tmp这个整数是否为偶数还是奇数。

  • 按位与运算符&:它会将两个操作数的二进制形式进行按位逻辑与运算。对于每一位,如果两个位都是1,则结果位为1;否则结果位为0。

  • 与1进行按位与运算:当我们对一个整数x执行x & 1时,实际上只检查x的最低位(最右边的位)。这是因为1的二进制形式为00000001(对于32位整数),与任何数的最低位进行按位与运算时,结果将直接反映该位的值。

  • 判断奇偶性:由于任何整数的奇偶性都由其最低位决定(奇数的最低位为1,偶数的最低位为0),x & 1的值将直接告诉我们x是奇数还是偶数。如果x & 1的结果为1,那么x是奇数;如果结果为0,那么x是偶数。

例如:

  • 如果tmp = 4,它的二进制表示为00000100,那么tmp & 1 = 0,因为00000100 & 00000001 = 00000000,所以tmp是偶数。
  • 如果tmp = 5,它的二进制表示为00000101,那么tmp & 1 = 1,因为00000101 & 00000001 = 00000001,所以tmp是奇数。

在代码中,tmp & 1用于判断tmp的和是否为偶数,从而决定是否需要进行替换操作以确保最终结果为偶数。


这行代码是算法中常见的一类更新最大值(或最小值)的操作,通常出现在动态规划、滑动窗口、贪心算法等解决最优化问题的场景中。我们来详细解析一下这行代码:

ans = Math.max(ans, tmp - even + cards[i]);

解析:

  1. ans:这通常表示当前已知的最优解。在算法执行过程中,ans 会不断更新,最终存储的是问题的最优解。

  2. Math.max():这是Java中的一个方法,用于返回两个参数中的最大值。这里它被用来比较当前的 ans 和一个新的计算值,如果新的计算值更大,就用它来更新 ans

  3. tmp:这可能是之前计算的一个中间值,它在算法中可能代表了某种累积效果或状态。

  4. even:这通常是一个常量或变量,可能代表某种固定的值,例如在特定问题中,even 可能是所有偶数元素的和,或者是某种需要从 tmp 中减去的值。

  5. cards[i]:这是一个数组 cards 中的第 i 个元素。在算法中,cards 可能代表一系列需要处理的输入数据。

代码逻辑:

这行代码的核心逻辑是在更新 ans,它比较了当前的 anstmp - even + cards[i] 的值,如果后者更大,那么 ans 就会被更新。这通常发生在需要在一系列操作或状态转换中找到最大值的场景,例如在动态规划问题中,我们经常需要根据之前的状态来计算当前状态的值,并在所有可能的状态中选择最大的那个作为最终状态。

示例场景:

假设我们在求解一个数组中的最大子数组和问题,其中 tmp 表示到当前位置为止的连续子数组和,even 是一个与问题无关的示例值(这里仅为了说明而加入,实际情况可能不同),cards[i] 表示当前元素。每次迭代时,我们通过 tmp - even + cards[i] 来计算更新后的子数组和,并与当前记录的最大值 ans 比较,如果新值更大,就更新 ans

总结:

这行代码是算法设计中更新最优解的常见模式,它体现了算法设计中比较和选择最优解的核心思想。


public class Solution {
public int maximumScore(int[] cards, int cnt) {
int[] frequency = new int[1001]; // 直接初始化数组

    // 计算每种数字出现的频率
    for (int card : cards) {
        frequency[card]++;
    }

    int score = 0;
    int remainingCount = cnt;
    int highestOdd = -1;
    int highestEven = -1;
    int lastTakenValue = -1;

    // 从大到小选择卡牌
    for (int i = 1000; i >= 1 && remainingCount > 0; i--) {
        if (frequency[i] > 0) {
            int takeCount = Math.min(frequency[i], remainingCount); // 取能取的最大数量
            score += takeCount * i; // 更新得分
            remainingCount -= takeCount; // 更新剩余需要选择的数量

            // 更新最高的奇数和偶数
            if (i % 2 == 0) {
                highestEven = i;
            } else {
                highestOdd = i;
            }

            // 更新上一次取的数字
            lastTakenValue = i;
        }
    }

    // 如果得分已经是偶数,则直接返回
    if (score % 2 == 0) {
        return score;
    }

    // 尝试替换一张卡牌以使总和变为偶数
    for (int i = lastTakenValue - 1; i >= 1 && remainingCount > 0; i--) {
        if (frequency[i] > 0) {
            if (highestOdd != -1 && i % 2 == 0) { // 替换偶数卡片
                return Math.max(score, score - highestEven + i);
            } else if (highestEven != -1 && i % 2 != 0) { // 替换奇数卡片
                return Math.max(score, score - highestOdd + i);
            }
        }
    }

    // 如果无法找到合适的替换方案,则返回 0
    return 0;
}

}
测试
public class Driver {
public static void main(String[] args) {
int[] param_1 = {1, 2, 3, 4, 5}; // 示例数组
int param_2 = 3; // 示例计数
Solution solution = new Solution();
int ret = solution.maximumScore(param_1, param_2);
System.out.println("Maximum Score: " + ret);
}
}在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布说在见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值