面试题40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
方法一:快速排序思想
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
//如果0个数或者数组为空,则返回空数组
if(k==0||arr.length==0)
{
return new int[0];
}
//返回最大下标为k-1的数组
return quickSearch(arr,0,arr.length-1,k-1);
}
public int[] quickSearch(int[] arr,int start,int end,int k)
{
//对数组进行切分,返回下标j
int j=partition(arr,start,end);
//比较下标j是否与我们所要求的个数相等
if(j==k)
{
return Arrays.copyOf(arr,j+1);//如果相等,则返回j及j左边的所有元素
}
//否则,根据j和k的大小决定继续切分左段还是右段
return j>k?quickSearch(arr,start,j-1,k):quickSearch(arr,j+1,end,k);
}
//返回下标j,使比arr[j]小的数在其左边,比arr[j]大的数在其右边
public int partition(int[]arr,int start,int end)
{
int v=arr[start];
int i=start;
int j=end+1;
while(true)
{
//从数组头向数组尾开始寻找,直到找到一个大于v的元素
while(++i<=end&&arr[i]<v);
//从数组尾向数组头开始寻找,直到找到一个小于v的元素
while(--j>=start&&arr[j]>v);
//如果i和j重逢,则跳出循环
if(i>=j)
{
break;
}
//交换i和j位置上的数字
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
//将v从数组头取出,放到它在数组中对应的位置,即左边所有数小于v,右边所有数大于v的位置
arr[start]=arr[j];
arr[j]=v;
return j;//返回该位置
}
}
方法二:大根堆
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[k];
int index=0;
for(int resNum:pq)
{
res[index++]=resNum;
}
return res;
}
}
知识点:
- PriorityQueue该队列的头部是相对于指定顺序的最小元素。
- 队列检索操作poll , remove ,peek和element访问在队列的头部的元件。
- public PriorityQueue(Comparator<? super E> comparator)
创建具有默认初始容量的 PriorityQueue ,并根据指定的比较器对其元素进行排序
方法三:二叉搜索树BST
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k==0||arr.length==0)
{
return new int[0];
}
TreeMap<Integer,Integer> tm=new TreeMap<>();
int count=0;
for(int num:arr)//遍历数组元素
{
if(count<k)//如果计数值没达到所想要的个数,则继续
{
tm.put(num,tm.getOrDefault(num,0)+1);//将新元素放入map中
count++;//计数加一
continue;
}
Map.Entry<Integer,Integer> entry=tm.lastEntry();
if(entry.getKey()>num)//如果map中最大键值大于新元素
{
tm.put(num,tm.getOrDefault(num,0)+1);//将新元素放入map中
if(entry.getValue()==1)//如果最大键值的val等于1,即只有一个
{
tm.pollLastEntry();//将该键值对poll出
}
else
{
tm.put(entry.getKey(),entry.getValue()-1);//否则,将该键值的val减一,即个数减一
}
}
}
int[] res=new int[k];
int index=0;
for(Map.Entry<Integer,Integer> entry:tm.entrySet())//遍历map中所有键值对
{
int countNum=entry.getValue();//先得到其val,即该key的个数
while(countNum-->0)
{
res[index++]=entry.getKey();//在新数组中放入相应个数的该key
}
}
return res;
}
}
知识点:
- public Set<Map.Entry<K,V>> entrySet()
返回此地图中包含的映射的Set视图。集合的迭代器按升序键顺序返回条目。 - public Map.Entry<K,V> lastEntry()
返回与该地图中最大键相关的键值映射,如果地图为空,则返回 null 。