LCP 40. 心算挑战---最大有效得分问题:原始方法与优化方法解析

「力扣挑战赛」心算项目的挑战比赛中,要求选手从 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

本文将详细解析这道题目的两种解法:原始方法优化方法。希望能为大家提供一些启发。

原始方法

思路
  1. 排序:首先对卡牌数组进行排序,以便于选择最大的卡牌。

  2. 选择前 cnt 个最大的数:从排序后的数组中选取前 cnt 个最大的数,并计算它们的总和。

  3. 检查总和的奇偶性
    (刚开始想简单了。既然要求在有限的数量内最大且必须和为偶数,所以先对数组排序然后从后往前遍历,当在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 的元素个数。这样可以避免对数组进行排序,从而提高效率。

实现步骤
  1. 计数数组:使用大小为 1001 的数组 val 来记录每个值的出现次数。

  2. 选择前 cnt 个最大的数:从高到低遍历 val 数组,选择前 cnt 个最大的数,并计算它们的总和。

  3. 检查总和的奇偶性:如果总和是偶数,直接返回总和;如果总和是奇数,找到未选中的最大偶数和最大奇数,尝试进行替换。

实现代码
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 是数组中元素的取值范围。

奇偶性调整

在算法中,我们经常需要处理奇偶性的调整问题。奇偶性调整涉及到在一组数中,通过替换某些元素,使得整体满足某些特定的奇偶性要求。这种技术在很多优化问题中都有应用。

总结

通过以上两种方法的比较,我们可以看到,优化方法在处理时间复杂度上有了显著提升。利用计数数组,我们避免了对数组进行排序,从而降低了时间开销。在实际应用中,我们需要根据具体问题选择合适的优化策略,以提升算法的效率。

希望这篇博客对大家有所帮助!如果有任何问题或进一步的讨论,欢迎留言交流。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值