题目
选自力扣
心算项目的挑战比赛中,要求选手从 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]);
解析:
-
ans
:这通常表示当前已知的最优解。在算法执行过程中,ans
会不断更新,最终存储的是问题的最优解。 -
Math.max()
:这是Java中的一个方法,用于返回两个参数中的最大值。这里它被用来比较当前的ans
和一个新的计算值,如果新的计算值更大,就用它来更新ans
。 -
tmp
:这可能是之前计算的一个中间值,它在算法中可能代表了某种累积效果或状态。 -
even
:这通常是一个常量或变量,可能代表某种固定的值,例如在特定问题中,even
可能是所有偶数元素的和,或者是某种需要从tmp
中减去的值。 -
cards[i]
:这是一个数组cards
中的第i
个元素。在算法中,cards
可能代表一系列需要处理的输入数据。
代码逻辑:
这行代码的核心逻辑是在更新 ans
,它比较了当前的 ans
和 tmp - 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);
}
}