找出最小的K个数——优先队列和选择算法(分治思想)两种方式

题目:设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

1、优先队列解法

(1)分析:

定义一个PriorityQueue类型的优先队列queue,在初始化时指定队列为固定大小k,只能存储k个元素,同时自定义优先队列的优先规则(排序规则)(PriorityQueue用法单独写个博客来梳理。从头到尾遍历数组元素,当queue中元素不等于k时,队列不满,数组中元素加入队列;当queue队列满时,判断队列头部与当前遍历的数组元素的大小,如果前者大,则队头出队,然后当前数组元素入队,如果后者大则不作处理继续遍历。重复上述过程直到数组遍历完毕。

(2)代码:

import java.util.*;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList result = new ArrayList();
       int length = input.length;

       // 特殊情况考虑
       if(k > length || k == 0){
           return result;
       }

        // 定义优先队列
        PriorityQueue maxHeap = new PriorityQueue(k, new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        for (int i = 0; i < length; i++) {
            if (maxHeap.size() != k) {

                // 队列不满
                maxHeap.offer(input[i]);
            } else if (maxHeap.peek() > input[i]) {

                // 队列已满
                Integer temp = maxHeap.poll();
                temp = null;
                maxHeap.offer(input[i]);
            }
        }
        for (Integer integer : maxHeap) {
            result.add(integer);
        }
        return result;
    }
}

2、选择算法(分治思想)

(1)分析:

类似于快速排序和求中位数时的算法(可参考:输油管道或水井挖水渠中路径和最短问题和快速排序和快速排序的随机化版本的解析)。整体思路是:选择一个主元,对主元进行处理,使得主元左侧的元素都不大于主元,主元右侧的元素都不小于主元。这个时候,如果主元处在k位置,则此主元就是第k小的元素。需要注意一点的是:这个方法是原址操作,直接修改的原数组。

(2)代码

import java.util.*;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList result = new ArrayList();
        int len = input.length;
        if(len == 0 || k > len || k==0){
            return result;
        }

        // 调用getMinK方法,安排第k小的元素放到k位置上
        getMinK(input,0,input.length-1,k);
        for(int i = 0; i < k; i++){
            result.add(input[i]);
        }
        return result;
    }
    
    //此问题是找出第k小的数值
    static void getMinK(int [] arr,int left,int right,int k){
        if(left == right){
            return;
        }
        int temIndex = random_partition(arr,left,right);
        int goalIndex = temIndex - left + 1;
        if(goalIndex == k){
            return;
        }else if(goalIndex > k){
            getMiddle(arr,left,temIndex-1,k);
        }else{
            getMiddle(arr,temIndex+1,right,k - goalIndex);
        }
    }


    static int random_partition(int [] arr,int left,int right){
        // 随机获取主元,可以是left到right之间的任意一个数,包括头尾
        int randomIndex = getRandom(left,right);
        // 随机获取的主元与最后一个元素交换位置
        if(randomIndex != right){
            swap(arr,randomIndex,right);
        }
        // 接下来通过多次比较和交换,找到主元的位置,并把主元放到最终的位置,同时返回主要最终位置的下标
        int leftIndex = left - 1;
        int mainFactor = arr[right];
        // 下标为right的不参与比较,j上界为right-1
        for(int j = left; j <= right - 1;j++){
            if(arr[j] < mainFactor){
                // leftIndex标定的元素是目前所知的比主元小的最右面的元素
                leftIndex++;
                swap(arr,leftIndex,j);
            }
        }
        // 下面这个交换是把主元放到最终的位置
        swap(arr,leftIndex+1,right);
        return leftIndex+1;
    }
    // 交换数组中下标为left和right的元素
    static void swap(int [] arr,int left,int right){
        int tem = arr[left];
        arr[left] = arr[right];
        arr[right] = tem;
    }
    // 获取min到max之间的随机一个数,包括min和max
    static int getRandom(int min,int max){
        // 返回[0.0,1.0)的double型数值
        return (int)(Math.random()*(max-min)+min);
    }
}

 

你可能感兴趣的:(算法题型专题)