题目描述与示例

题目描述

一贫如洗的樵夫阿里巴巴在去砍柴的路上,无意中发现了强盗集团的藏宝地,藏宝地有编号从0~N的箱子,每个箱子上面贴有箱子中藏有金币的数量。

从金币数量中选出一个数字集合,并销毁贴有这些数字的每个箱子,如果能销毁一半及以上的箱子,则返回这个数字集合的最小大小。

输入描述

一个数字字串,数字之间使用逗号分隔,例如: 6,6,6,6,3,3,3,1,1,5

字符串中数字的个数为偶数;

1 <= 数字个数 <=100000

1 <= ``数字`` ``<=100000

输出描述

这个数字集合的最小大小,例如: 2

示例一

输入

1,1,1,1,3,3,3,6,6,8

输出

2

说明

选择集合{1,8},销毁后的结果数组为[3,3,3,6,6],长度为 5,长度为原数组的一半。

大小为2的可行集合还有{1,3},{1,6},{3,6}

选择6,8 集合是不可行的,它销毁后的结果数组为[1,1,1,1,3,3,3],新数组长度大于原数组的二分之一。

示例二

输入

2,2,2,2

输出

1

说明

我们只能选择集合{2},销毁后的结果数组为空

解题思路

贪心地思考问题,优先选择出现次数多的箱子进行销毁即可。统计同种箱子出现次数,需要用到计数器Counter

代码

Python

# 题目:2023B-阿里巴巴找黄金宝箱2
# 分值:100
# 作者:闭着眼睛学数理化
# 算法:贪心
# 代码看不懂的地方,请直接在群上提问


from collections import Counter
from math import ceil

# 输入所有数字
lst = list(map(int, input().split(",")))
# 统计数字出现的次数
cnt = Counter(lst)
# 逆序排序所有数字出现次数,用于后续的贪心过程
nums = list(sorted(cnt.values(), reverse = True))
# 需要挑出的目标和,为lst长度的一半(向上取整)
target = ceil(len(lst) / 2)
# 已经挑出的数字的个数
total = 0
# 答案变量
ans = 0
# 遍历nums中的每一个数字num
for num in nums:
    # 将num加入total中
    total += num
    # 选择了一种集合,ans递增
    ans += 1
    # 如果加完num后,total超过了目标和,输出答案ans,同时退出循环
    if total >= target:
        print(ans)
        break

Java

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        String[] input = scanner.nextLine().split(",");
        int[] lst = Arrays.stream(input).mapToInt(Integer::parseInt).toArray();

        Map<Integer, Integer> cnt = new HashMap<>();
        for (int num : lst) {
            cnt.put(num, cnt.getOrDefault(num, 0) + 1);
        }

        int[] nums = cnt.values().stream().mapToInt(Integer::intValue).toArray();
        Arrays.sort(nums);
        int target = (int) Math.ceil(lst.length / 2.0);
        int total = 0;
        int ans = 0;

        for (int i = nums.length - 1; i >= 0; i--) {
            total += nums[i];
            ans++;

            if (total >= target) {
                System.out.println(ans);
                break;
            }
        }
    }
}

C++

“`C++
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <cmath>

using namespace std;

int main() {
string input;
getline(cin, input);

<pre><code>vector<string> inputTokens;
size_t pos = 0;
while ((pos = input.find(",")) != string::npos) {
inputTokens.push_back(input.substr(0, pos));
input.erase(0, pos + 1);
}
inputTokens.push_back(input);

vector<int> lst;
for (const string& token : inputTokens) {
lst.push_back(stoi(token));
}

map<int, int> cnt;
for (int num : lst) {
cnt[num]++;
}

vector<int> nums;
for (const auto& kv : cnt) {
nums.push_back(kv.second);
}
sort(nums.rbegin(), nums.rend());

int target = ceil(static_cast<double>(lst.size()) / 2);
int total = 0;
int ans = 0;

for (int num : nums) {
total += num;
ans++;

if (total >= target) {
cout << ans << endl;
break;
}
}

return 0;
</code></pre>

}

“`

时空复杂度

时间复杂度:O(``NlogN``)。排序的时间复杂度。

空间复杂度:O(``1``)。仅需若干常数变量。

说明

华为OD机试有三道题⽬,第⼀道和第⼆道属于简单或中等题,分值为 100 分,第三道为中等或困难题,分值为 200分,总分为 400 分。

机试分数越⾼评级越⾼,⼯资也就越⾼。

关于华为 OD 机试更加详细的介绍可以查看这篇⽂章:华为OD机考须知

关于机考题目汇总可以看这篇文章:华为OD机试真题 2023 A+B+C+D卷 + 2024新卷(Python&Java&C++)⽬录汇总(每⽇更新)