计数排序(Counting Sort)是一种线性时间复杂度的排序算法,适用于整数数据。它的基本思想是将输入的数据值转化为键存储在额外开辟的数组空间中。计数排序不是基于比较的排序算法,因此它可以突破 O(n log n) 的时间下界,实现线性时间排序。
计数排序的工作原理:
- 找出待排序数组的最大值和最小值:确定统计数组的长度和初始化统计数组。
- 统计数组中每个值出现的次数:遍历待排序数组,统计每个元素出现的次数,存入统计数组对应的索引位置。
- 根据统计数组得到排序后的数组:遍历统计数组,按照元素出现的次数依次将元素放入新的数组中。
计数排序的适用条件:
- 待排序数组中的元素是整数,且范围不是很大。
- 待排序数组中的元素重复次数较多,且分布较为集中。
计数排序的优缺点:
优点:
- 计数排序可以突破 O(n log n) 的时间下界,实现线性时间复杂度 O(n + k),其中 n 是待排序数组的长度,k 是数组中的最大值。
- 计数排序是稳定的排序算法,相同元素的相对位置不会改变。
缺点:
- 计数排序需要额外的空间来存储计数数组,空间复杂度为 O(k),其中 k 是待排序数组中的最大值。
- 当元素是负数或者范围很大时,计数排序的效率会降低。
计数排序的Java实现:
public class CountingSort {
public int[] countingSort(int[] arr) {
if (arr == null || arr.length == 0) {
return arr;
}
// 找出数组的最大值和最小值
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
// 初始化计数数组
int range = max - min + 1;
int[] count = new int[range];
for (int value : arr) {
count[value - min]++;
}
// 根据计数数组得到排序后的数组
int[] sortedArr = new int[arr.length];
int index = 0;
for (int i = 0; i < count.length; i++) {
for (int j = 0; j < count[i]; j++) {
sortedArr[index++] = i + min;
}
}
return sortedArr;
}
public static void main(String[] args) {
CountingSort countingSort = new CountingSort();
int[] arr = {3, 1, 2, 0, 2, 1};
int[] sortedArr = countingSort.countingSort(arr);
System.out.println("Sorted array: " + Arrays.toString(sortedArr));
}
}
在实际应用中,计数排序特别适合于处理大量重复数据的数组排序问题。例如,对于一个学生成绩的数组,使用计数排序可以快速地按照成绩进行排序。在面试中,了解和掌握计数排序的原理和实现是非常有益的。计数排序主要适用于整数数据的排序,特别是当数据范围不是很大,且重复元素较多时,计数排序能够提供非常高效的解决方案。以下是三道与计数排序相关的面试题目,以及相应的Java源码实现。
题目 1:固定范围的整数排序
描述:
给定一个整数数组,数组中的元素范围在 [0, k] 之间,你需要对这个数组进行排序。
示例:
输入: nums = [4, 2, 2, 6, 1, 0], k = 7
输出: [0, 1, 2, 2, 4, 6]
Java 源码:
public class FixedRangeSort {
public int[] sortIntegers(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return nums;
}
// 初始化计数数组
int[] count = new int[k + 1];
for (int num : nums) {
count[num]++;
}
// 根据计数数组得到排序后的数组
int[] sortedArr = new int[nums.length];
int index = 0;
for (int i = 0; i <= k; i++) {
while (count[i] > 0) {
sortedArr[index++] = i;
count[i]--;
}
}
return sortedArr;
}
public static void main(String[] args) {
FixedRangeSort solution = new FixedRangeSort();
int[] nums = {4, 2, 2, 6, 1, 0};
int k = 7;
int[] sortedArr = solution.sortIntegers(nums, k);
System.out.println("Sorted array: " + Arrays.toString(sortedArr));
}
}
题目 2:最少数量的硬币更改
描述:
给定一个硬币系统,coins = [1, 2, 5],表示你可以用 1,2,和 5 元硬币来支付。给定一个总金额 amount,询问你需要的最少硬币数量。
如果没有任何硬币可以达到总金额,则返回 -1。
示例:
输入: coins = [1, 2, 5], amount = 11
输出: 3 (11 = 5 + 5 + 1)
Java 源码:
public class MinCoinChange {
public int minCoinChange(int[] coins, int amount) {
int[] count = new int[amount + 1];
// 初始化计数数组,count[i] 表示达到金额 i 需要的最少硬币数
for (int i = 1; i <= amount; i++) {
count[i] = Integer.MAX_VALUE;
}
// 0 金额不需要硬币
count[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i - coin >= 0 && count[i - coin] != Integer.MAX_VALUE) {
count[i] = Math.min(count[i], count[i - coin] + 1);
}
}
}
return count[amount] == Integer.MAX_VALUE ? -1 : count[amount];
}
public static void main(String[] args) {
MinCoinChange solution = new MinCoinChange();
int[] coins = {1, 2, 5};
int amount = 11;
int result = solution.minCoinChange(coins, amount);
System.out.println("Minimum coins needed: " + result);
}
}
题目 3:统计元素出现次数
描述:
给定一个整数数组,统计数组中每个元素出现的次数。
示例:
输入: nums = [1, 1, 3, 4, 4, 4, 5]
输出: [2, 1, 0, 3, 3, 0, 1]
Java 源码:
public class CountElementFrequency {
public int[] countElementFrequency(int[] nums) {
if (nums == null || nums.length == 0) {
return new int[0];
}
int max = Arrays.stream(nums).max().getAsInt();
int[] count = new int[max + 1];
for (int num : nums) {
count[num]++;
}
// 过滤掉从未出现过的元素
return Arrays.stream(count).filter(c -> c > 0).toArray();
}
public static void main(String[] args) {
CountElementFrequency solution = new CountElementFrequency();
int[] nums = {1, 1, 3, 4, 4, 4, 5};
int[] frequencies = solution.countElementFrequency(nums);
System.out.println("Element frequencies: " + Arrays.toString(frequencies));
}
}
这些题目和源码展示了计数排序在不同场景下的应用,包括基本的排序问题、硬币找零问题以及统计元素出现次数问题。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!