【剑指offer-JZ40】最小的K个数

115 阅读2分钟

题目要求:O(n*logn)。求最小的k个数

第一种方法:最小堆,O(n*logk)版本。完全满足时间要求

import java.util.ArrayList;

public class Solution {

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }


    // 非递归调整方法
    public static void adjustHeap(int[] array, int i, int length) {
        // 先把当前元素取出来,因为当前元素可能要一直移动
        int temp = array[i];
        // 传进来当前结点编号i, k = 2*i+1 和 k+1 分别是他的左右孩子
        // for循环的目的是:如果当前结点已经很靠上了,如果调整一下可能会影响下面的子树,所以要持续地往下检查是否满足要求
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { // 结点从i开始一个一个处理,直到
            // 这个if语句的目的是判断左右孩子节点哪个大。k是左孩子,k+1是右孩子。如果右孩子大,就k++,代表k指向右子结点
            if (k + 1 < length && array[k] > array[k + 1]) {
                k++;
            }
            // 判断孩子节点中大较大者是否比父节点值要大。如果是,就交换,然后进入下一个for循环,检查子树是否还满足要求;如果不是,就break跳出函数,重新传入前一个非终端节点
            if (array[k] < temp) {   //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                swap(array, i, k);  // 交换
                i = k;   // 为了配合下一个for循环堆其子树的检查,这里要更新一下当前位置
            } else {
                break;
            }
        }
        //arr[i] = temp; // 这一句加不加都可以。
    }
    public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list  = new ArrayList<>();
        // 建初堆
        int len = input.length;
        for (int i = len / 2 - 1; i >= 0; i--) {
            adjustHeap(input, i, input.length);   // i初始为len/2-1,表示它是最后一个非终端结点。从这里开始i--,一直往前分析
        }
        // 调整堆
        for (int j = len - 1; j > len - 1-k; j--) {
            list.add(input[0]);
            swap(input, 0, j); // 元素交换,把大顶堆的根元素,放到数组的最后
            adjustHeap(input, 0, j); // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。调整剩下的元素从新变成堆
        }
        return list;
    }

    public static void main(String[] args){
         int[] arr = {4,5,1,6,2,7,3,8};
        System.out.println(GetLeastNumbers_Solution(arr,4));
    }
}

第一种方法(补充):巧妙地运用用最大堆求前k小的值,比O(n*logk)还要小一些。完全满足时间要求

import java.util.ArrayList;
public class Solution {
    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void adjustHeap(int[] array, int i, int length) {
        int temp = array[i];
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
            if (k + 1 < length && array[k] < array[k + 1]) {
                k++;
            }
            if (array[k] > temp) {
                swap(array, i, k);
                i = k;
            } else {
                break;
            }
        }
    }

    public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        int[] arr = new int[k];  //用于放最小的k个数
        for (int i = 0; i < k; i++)
            arr[i] = input[i];//先放入前k个数
        // 建立前k个无序元素的初堆
        int len = arr.length;
        for (int i = len / 2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, len);   // i初始为len/2-1,表示它是最后一个非终端结点。从这里开始i--,一直往前分析
        }
        // 动态调整堆
        for (int i = k; i < input.length; i++) {
            // 此时arr[0]是前k个无序元素的最大者。然后比较原无序数组里前k个元素后面是否还有比这个最大值小的,如果有,就替换掉,再重新调整堆
            if (input[i] < arr[0]) { //存在更小的数字时
                arr[0] = input[i];  // 替换
                adjustHeap(arr, 0, k - 1);//重新调整最大堆。因为数组的末端是已经求得的最小值,不能再动了,所以每次要k-1
            }
        }
        // 最后arr中剩下的就是原无序数组里前k小的值
        for (int i : arr)
            list.add(i);
        return list;
    }

    public static void main(String[] args) {
        int[] arr = {4, 5, 1, 6, 2, 7, 3, 8};
        System.out.println(GetLeastNumbers_Solution(arr, 4));
    }
}



第二种方法:快速排序,时间复杂度为O(nlogn)。刚好满足时间要求。 如果使用Partition函数,回达到O(n),完全符合要求



第三种方法:冒泡排序,O(n*k),不满足时间要求。但是也可以选出前k小的数(外层循环次数设为k)

static void bubbleSort1() {
    for (int i = 0; i <k; i++) {
        for (int j = 0; j < len-i-1; j++) {
            if (arr[j] < arr[j + 1]) {
                swap(arr, j, j + 1);  //将数组中的两个数交换
            }
        }
    }
}


题源:www.nowcoder.com/practice/6a…