「力扣挑战赛」心算项目的挑战比赛中,要求选手从 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 <= cnt <= cards.length <= 10^5
1 <= cards[i] <= 1000
本文将详细解析这道题目的两种解法:原始方法与优化方法。希望能为大家提供一些启发。
原始方法
思路
-
排序:首先对卡牌数组进行排序,以便于选择最大的卡牌。
-
选择前
cnt
个最大的数:从排序后的数组中选取前cnt
个最大的数,并计算它们的总和。 -
检查总和的奇偶性
(刚开始想简单了。既然要求在有限的数量内最大且必须和为偶数,所以先对数组排序然后从后往前遍历,当在cnt个数内容进行累加加到cnt-1时需要特判总和的奇偶性,如果是偶数直接加,如果是奇数则判断下一个数的奇偶性重复判断步骤。
但是测试数据 [9,5,9,1,6,10,3,4,5,1] 2 直接打脸: 9 + 9 > 10 + 6
)-
如果总和是偶数,则已经是最优解,直接返回总和。
-
如果总和是奇数,则需要进行调整。我们在已选中的部分找到最小的奇数,尝试用未选中的部分最大的偶数替换它,或者找到最小的偶数,尝试用未选中的部分最大的奇数替换它。
-
实现代码
import java.util.Arrays;
public class Solution {
public int maxmiumScore(int[] cards, int cnt) {
Arrays.sort(cards);
int n = cards.length;
int sum = 0;
// 选取前 cnt 个最大的数并计算总和
for (int i = n - 1; i >= n - cnt; i--) {
sum += cards[i];
}
// 检查总和的奇偶性
if (sum % 2 == 0) {
return sum;
}
// 记录已选中的最小奇数和最小偶数
int minOddInSelected = -1;
int minEvenInSelected = -1;
for (int i = n - cnt; i < n; i++) {
if (cards[i] % 2 == 0) {
if (minEvenInSelected == -1 || cards[i] < minEvenInSelected) {
minEvenInSelected = cards[i];
}
} else {
if (minOddInSelected == -1 || cards[i] < minOddInSelected) {
minOddInSelected = cards[i];
}
}
}
// 记录未选中的最大奇数和最大偶数
int maxOddOutsideSelected = -1;
int maxEvenOutsideSelected = -1;
for (int i = 0; i < n - cnt; i++) {
if (cards[i] % 2 == 0) {
if (maxEvenOutsideSelected == -1 || cards[i] > maxEvenOutsideSelected) {
maxEvenOutsideSelected = cards[i];
}
} else {
if (maxOddOutsideSelected == -1 || cards[i] > maxOddOutsideSelected) {
maxOddOutsideSelected = cards[i];
}
}
}
// 尝试进行替换
int maxSum = 0;
if (minOddInSelected != -1 && maxEvenOutsideSelected != -1) {
maxSum = Math.max(maxSum, sum - minOddInSelected + maxEvenOutsideSelected);
}
if (minEvenInSelected != -1 && maxOddOutsideSelected != -1) {
maxSum = Math.max(maxSum, sum - minEvenInSelected + maxOddOutsideSelected);
}
return maxSum == 0 ? 0 : maxSum;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] cards = {9, 5, 9, 1, 6, 10, 3, 4, 5, 1};
int cnt = 2;
System.out.println(solution.maxmiumScore(cards, cnt)); // 输出应为19
}
}
优化方法
思路
为了进一步优化时间复杂度,我们注意到 cards[i]
的取值范围是 [1, 1000]
,因此可以使用计数数组 val
,其中 val[k]
表示值为 k
的元素个数。这样可以避免对数组进行排序,从而提高效率。
实现步骤
-
计数数组:使用大小为 1001 的数组
val
来记录每个值的出现次数。 -
选择前
cnt
个最大的数:从高到低遍历val
数组,选择前cnt
个最大的数,并计算它们的总和。 -
检查总和的奇偶性:如果总和是偶数,直接返回总和;如果总和是奇数,找到未选中的最大偶数和最大奇数,尝试进行替换。
实现代码
import java.util.Arrays;
public class Solution {
public int maxmiumScore(int[] cards, int cnt) {
int[] val = new int[1001];
// 计数数组记录每个值的出现次数
for (int card : cards) {
val[card]++;
}
int sum = 0;
int selectedCount = 0;
int minOddInSelected = Integer.MAX_VALUE;
int minEvenInSelected = Integer.MAX_VALUE;
// 从高到低遍历计数数组,选择前 cnt 个最大的数
for (int i = 1000; i >= 1 && selectedCount < cnt; i--) {
while (val[i] > 0 && selectedCount < cnt) {
sum += i;
selectedCount++;
val[i]--;
// 记录已选择的最小奇数和最小偶数
if (i % 2 == 0) {
minEvenInSelected = Math.min(minEvenInSelected, i);
} else {
minOddInSelected = Math.min(minOddInSelected, i);
}
}
}
// 如果总和是偶数,直接返回总和
if (sum % 2 == 0) {
return sum;
}
// 记录未选中的最大奇数和最大偶数
int maxOddOutsideSelected = Integer.MIN_VALUE;
int maxEvenOutsideSelected = Integer.MIN_VALUE;
for (int i = 1; i <= 1000; i++) {
while (val[i] > 0) {
if (i % 2 == 0) {
maxEvenOutsideSelected = Math.max(maxEvenOutsideSelected, i);
} else {
maxOddOutsideSelected = Math.max(maxOddOutsideSelected, i);
}
val[i]--;
}
}
// 尝试进行替换
int maxSum = 0;
if (minOddInSelected != Integer.MAX_VALUE && maxEvenOutsideSelected != Integer.MIN_VALUE) {
maxSum = Math.max(maxSum, sum - minOddInSelected + maxEvenOutsideSelected);
}
if (minEvenInSelected != Integer.MAX_VALUE && maxOddOutsideSelected != Integer.MIN_VALUE) {
maxSum = Math.max(maxSum, sum - minEvenInSelected + maxOddOutsideSelected);
}
return maxSum == 0 ? 0 : maxSum;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] cards = {9, 5, 9, 1, 6, 10, 3, 4, 5, 1};
int cnt = 2;
System.out.println(solution.maxmiumScore(cards, cnt)); // 输出应为19
}
}
小知识点
计数排序(Counting Sort)
计数排序是一种非比较排序算法,它的核心思想是通过统计数组中元素的出现次数来进行排序。它适用于取值范围较小的整数排序。计数排序的时间复杂度是 O(n+k),其中 n 是数组的长度,k 是数组中元素的取值范围。
奇偶性调整
在算法中,我们经常需要处理奇偶性的调整问题。奇偶性调整涉及到在一组数中,通过替换某些元素,使得整体满足某些特定的奇偶性要求。这种技术在很多优化问题中都有应用。
总结
通过以上两种方法的比较,我们可以看到,优化方法在处理时间复杂度上有了显著提升。利用计数数组,我们避免了对数组进行排序,从而降低了时间开销。在实际应用中,我们需要根据具体问题选择合适的优化策略,以提升算法的效率。
希望这篇博客对大家有所帮助!如果有任何问题或进一步的讨论,欢迎留言交流。