携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情
一、前言
桶排序 (Bucket sort
) 或所谓的箱排序 :工作的原理是将数组分到有限数量的桶子里。
- 每个桶子再个别排序(插入排序)。
- 桶排序是 鸽巢排序 的一种归纳结果。
桶排序,是一个稳定排序: 适用于数据是均匀分布的情况,这样可以让分布到各个桶内的元素数量相当。
- 最快情况:数据可以均匀分配到每一个桶中。
- 最慢情况:数据被分配到同一个桶中。
# 举个栗子,有 100 200 300 ... 10000,总共 100 个且均匀分布
# 如果使用计数排序,则需要创建辅助数组长度为 10000
# 所以,面对数据是均匀分布的情况,使用桶排序
代码实现如下:
public class BucketSort {
private int bucketSize;
public BucketSort(int bucketSize) {
this.bucketSize = bucketSize;
}
// Time(avg): O(n+k), Time(worst): O(n^2), Space: O(n)
public void sort(int[] arr) {
if (arr == null || arr.length == 0) return;
int max = arr[0], min = arr[0];
// 计算最大值和最小值
for (int num : arr) {
if (num > max) max = num;
if (num < min) min = num;
}
// 桶的数量 = 数组长度 / 桶大小
int bucketCount = arr.length / bucketSize;
List<List<Integer>> buckets = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; ++i)
buckets.add(new ArrayList<>());
for (int num : arr) {
// 计算放在哪个桶中
int idx = (int) ((num - min) / (max - min + 1.0) * bucketCount);
buckets.get(idx).add(num);
}
int idx = 0;
for (List<Integer> bucket : buckets) {
insertionSort(bucket); // 桶内:插入排序
for (int num : bucket) { // 再放入结果中
arr[idx++] = num;
}
}
}
private void insertionSort(List<Integer> arr) {
if (arr == null || arr.size() == 0) return;
for (int i = 1; i < arr.size(); ++i) {
int cur = arr.get(i);
int j = i - 1;
while (j >= 0 && arr.get(j) > cur) { // 先找一个合适的位置
arr.set(j + 1, arr.get(j));
--j;
}
// 找到合适的位置,放下
arr.set(j + 1, cur);
}
}
}
二、题目
(1)前 K 个高频数字(中)
题干分析
这个题目说的是,给你一个不为空的整数数组,你要返回前 K 个出现频率最高的数字。假设给你的 K 总是有效的,也就是数组中一定包含至少 K 个不同的数字。
# 比如说,给你的数组是:
1, 2, 1, 2, 1, 4
# 给你的 K 是 2:
K = 2
# 在这个数组中,数字 1 出现了 3 次,数字 2 出现 2 次,数字 4 只出现 1 次。
# 因此,出现频率最高的两个数字是:
[1, 2]
注意,返回结果中,数字的顺序不重要,也就是说这里返回 [2, 1] 也是对的。
思路解法
思路方法有三: 暴力法(哈希表存储)、快速选择 和 桶排序
方法三:桶排序
- 用哈希表:统计数字出现的次数。
- 定义桶:把数字放入桶下。
- 从后向前遍历桶,取 k 个数组即可。
// 方法三: 桶排序
// Time: O(n), Space: O(n), Faster: 97.25%
public int[] topKFrequentBucketSort(int[] nums, int k) {
// 1. 哈希表:统计数字出现的次数
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num: nums) {
int freq = freqMap.getOrDefault(num, 0);
freqMap.put(num, freq + 1);
}
// 2. 定义桶
List<List<Integer>> buckets = new ArrayList<>(nums.length + 1);
for (int i = 0; i <= nums.length; ++i) buckets.add(new ArrayList<>());
for (Map.Entry<Integer, Integer> e: freqMap.entrySet()) {
buckets.get(e.getValue()).add(e.getKey());
}
// 3. 从后往前取 k 个元素。
List<Integer> result = new ArrayList<>();
for (int i = buckets.size() - 1; i >= 0 && k > 0; --i) {
List<Integer> bucket = buckets.get(i);
for (int j = 0; j < bucket.size() && k > 0; ++j) {
result.add(bucket.get(j));
--k;
}
}
int[] ans = new int[result.size()];
for (int i = 0; i < result.size(); ++i) {
ans[i] = result.get(i);
}
return ans;
}