题目描述
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
我的题解
暴力排序
把数组升序排好,取出最前的k个即可,很暴力,比较低效, 仅适合与k接近与数组长度。
//java 8ms
public int[] getLeastNumbers(int[] arr, int k) {
Arrays.sort(arr);
int [] res = new int[k];
for (int i=0;i<k;i++){
res[i]=arr[i];
}
return res;
}
有限排序
想法是只找出k个最小数,不要对其他的数进行排序
//效率还是不行,比上面全排序还差,这个排序算法需要改进!
public int[] getLeastNumbers(int[] arr, int k) {
int len = arr.length;
int [] res = new int[k];
for (int i=0;i<k;i++){
int min = arr[i];
int minIndex=i;
for (int j=i+1;j<len;j++){
if (arr[j]<min){
min = arr[j];
minIndex=j;
}
}
if (minIndex!=i) arr[minIndex]=arr[i];
res[i]=min;
}
return res;
}
利用快速排序
快排:
private int [] quickSort(int []arr,int left,int right){//right=数组长-1
int L=left,R=right;
if (L<R){//至少2两个元素
int tmp = arr[L];//取基准
while (L<R){
//找出右边比基准小的第一个数,
while (L<R && arr[R]>=tmp)R--;
//找到了之后拿到左边:如果没有匹配的则L==R,相当于没替换
arr[L]=arr[R];
//找出左边比基准大的第一个元素
while (L<R && arr[L]<=tmp)L++;
//找到之后拿到右边
arr[R]=arr[L];
}
//跳出循环是L==R,arr[L]是中间的那个数,大小数居两边;
//注意上面:去掉内部while,发现执行了L=R’,R‘=L‘,即这个基准被改变了,需要还原:
arr[L] = tmp;
//那么左右递归,对子数组排序
quickSort(arr,left,L-1);
quickSort(arr,R+1,right);
}
return arr;
}
快排是选一个分解值,然后把小于的移到一边,大于的移到另一边,会返回分界值所在的下标。
这里每次找一个分界值,得出其下标m,m和K比较:
k=m:说明刚好找到
k<m:说明top k在前m个,递归(0,m-1)
k>m:说明还有m-k个数在(m+1,R)中,递归
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0) return new int[0];
else if(arr.length<=k)return arr;
topK(arr,0,arr.length-1,k);
int res [] = new int[k];
for (int i=0;i<k;i++)res[i]=arr[i];
return res;
}
private void topK(int []a,int left,int right,int k){
int m=partition(a,left,right);
if (m==k)return ;
else if (m>k)topK(a,left,m-1,k);
else topK(a,m+1,right,k);
}
//分区函数:
private int partition(int []a,int L,int R){
int tmp = a[L];
while (L!=R){
while (L<R && a[R]>=tmp)R--;
a[L]=a[R];
while(L<R && a[L]<=tmp)L++;
a[R]=a[L];
}
a[R]=tmp;
return R;
}
这里分区函数是将快排的递归为1的时候,这个赋值比较多,性能其实不太好
看到这个:
int partition_better(int[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
int v = a[lo];
while (true) {
while (a[++i] < v) //左到右扫描,找到第一个 >= v的
if (i == hi) break;//没有,退出
while (a[--j] > v)//右到左扫描,找到第一个 <= v的
if (j == lo)break;//没有,退出
if (i >= j) break;//扫描完毕,退出
swap(a, i, j);//找到后交换值
}
swap(a, lo, j);//j是分界下标,把分界值拿过去
return j;
}
void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
其他题解
最大堆 O(NlogK)
// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1. 若目前堆的大小小于K,将当前数字放入堆中。
// 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
// 反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 默认是小根堆,实现大根堆需要重写一下比较器。
Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
for (int num: arr) {
if (pq.size() < k) {
pq.offer(num);
} else if (num < pq.peek()) {
pq.poll();
pq.offer(num);
}
}
// 返回堆中的元素
int[] res = new int[pq.size()];
int idx = 0;
for(int num: pq) {
res[idx++] = num;
}
return res;
}
}
二叉搜索树
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// TreeMap的key是数字, value是该数字的个数。
// cnt表示当前map总共存了多少个数字。
TreeMap<Integer, Integer> map = new TreeMap<>();
int cnt = 0;
for (int num: arr) {
// 1. 遍历数组,若当前map中的数字个数小于k,则map中当前数字对应个数+1
if (cnt < k) {
map.put(num, map.getOrDefault(num, 0) + 1);
cnt++;
continue;
}
// 2. 否则,取出map中最大的Key(即最大的数字), 判断当前数字与map中最大数字的大小关系:
// 若当前数字比map中最大的数字还大,就直接忽略;
// 若当前数字比map中最大的数字小,则将当前数字加入map中,并将map中的最大数字的个数-1。
Map.Entry<Integer, Integer> entry = map.lastEntry();
if (entry.getKey() > num) {
map.put(num, map.getOrDefault(num, 0) + 1);
if (entry.getValue() == 1) {
map.pollLastEntry();
} else {
map.put(entry.getKey(), entry.getValue() - 1);
}
}
}
// 最后返回map中的元素
int[] res = new int[k];
int idx = 0;
for (Map.Entry<Integer, Integer> entry: map.entrySet()) {
int freq = entry.getValue();
while (freq-- > 0) {
res[idx++] = entry.getKey();
}
}
return res;
}
}
计数排序
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 统计每个数字出现的次数
int[] counter = new int[10001];
for (int num: arr) {
counter[num]++;
}
// 根据counter数组从头找出k个数作为返回结果
int[] res = new int[k];
int idx = 0;
for (int num = 0; num < counter.length; num++) {
while (counter[num]-- > 0 && idx < k) {
res[idx++] = num;
}
if (idx == k) {
break;
}
}
return res;
}
}
哈希
int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
int hash[10001] = {0};
int *ret;
int i,j;
if(k >= arrSize) {
*returnSize = arrSize;
return arr;
}
ret = malloc(sizeof(int) * k);
for(i = 0; i < arrSize; i++) {
hash[arr[i]]++;
}
i = 0; j = 0;
while(i < k) {
if(hash[j]) {
ret[i] = j;
hash[j]--;
i++;
} else {
j++;
}
}
*returnSize = k;
return ret;
}
桶计数
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> ret;
int m[10002];
memset(m, 0, sizeof(m));
for (auto i : arr) {
m[i] ++;
}
for (int i = 0; i < 10002; i ++) {
if (k <= 0) {
break;
}
int count = k < m[i] ? k : m[i];
while (count --) {
ret.push_back(i);
}
k -= m[i];
}
return ret;
}
};