剑指 Offer 40. 最小的k个数

题目:

思路:

【1】简单点的思路就是先排序,再取出K个最小的值,如果没有做限制的话,使用sort函数就好了,如果限制函数的话,其实可以考虑快速排序和选择排序,快排的话不一定比选择排序更优,如果K很小的话其实选择排序会比快排更快,不过冒泡和选择两种排序的时间复杂度都是O(N)。但是解答时候明显使用快排的话比使用内置函数要快。

【2】使用小顶堆的方式:但其实JAVA里面并没有堆这个结构,一般都是二叉树的自我实现,其实大小顶堆可以参考 PriorityQueue 优先队列的源码,通过上浮和下沉的方式,利用满二叉树的概念构建的大小顶堆来形成优先队列的概念。

代码展示:

使用堆概念的方法:

//时间15 ms击败29.2%
//内存42.9 MB击败18.97%
//这种是全部塞入,然后根据多少来取出,因为本身就是小顶堆
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0) return new int[0];

        int v[] = new int[k];

        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            pq.offer(arr[i]);
        }

        for (int i = 0; i < k; i++) {
            v[i] = pq.poll();
        }

        return v;
    }
}

//时间16 ms击败25.23%
//内存43.1 MB击败10.76%
//时间复杂度:O(nlog⁡k),其中 n 是数组 arr 的长度。由于大根堆实时维护前 kkk 小值,所以插入删除都是 O(log⁡k) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlog⁡k) 的时间复杂度。
//空间复杂度:O(k),因为大根堆里最多 k 个数。
//这种是构建大顶堆,所以塞入的个数其实是K个,本身也能达到目的
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
            public int compare(Integer num1, Integer num2) {
                return num2 - num1;
            }
        });
        //利用java8函数表达式的简化写法
        //PriorityQueue<Integer> queue = new PriorityQueue<Integer>((a, b) -> (b - a));
        for (int i = 0; i < k; ++i) {
            queue.offer(arr[i]);
        }
        for (int i = k; i < arr.length; ++i) {
            if (queue.peek() > arr[i]) {
                queue.poll();
                queue.offer(arr[i]);
            }
        }
        for (int i = 0; i < k; ++i) {
            vec[i] = queue.poll();
        }
        return vec;
    }
}

 

使用快排的方式:

//时间2 ms击败93.24%
//内存42.7 MB击败32.16%
//时间复杂度:期望为 O(n) ,最坏情况下的时间复杂度为 O(n^2)。情况最差时,每次的划分点都是最大值或最小值,一共需要划分 n−1 次,而一次划分需要线性的时间复杂度,所以最坏情况下时间复杂度为 O(n^2)。
//空间复杂度:期望为 O(log⁡n),递归调用的期望深度为 O(log⁡n),每层需要的空间为 O(1),只有常数个变量。最坏情况下的空间复杂度为 O(n)。最坏情况下需要划分 n 次,即 randomized_selected 函数递归调用最深 n−1 层,而每层由于需要 O(1) 的空间,所以一共需要 O(n) 的空间复杂度
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        randomizedSelected(arr, 0, arr.length - 1, k);
        int[] vec = new int[k];
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec;
    }

    private void randomizedSelected(int[] arr, int l, int r, int k) {
        if (l >= r) {
            return;
        }
        int pos = randomizedPartition(arr, l, r);
        int num = pos - l + 1;
        if (k == num) {
            return;
        } else if (k < num) {
            randomizedSelected(arr, l, pos - 1, k);
        } else {
            randomizedSelected(arr, pos + 1, r, k - num);
        }
    }

    // 基于随机的划分
    private int randomizedPartition(int[] nums, int l, int r) {
        int i = new Random().nextInt(r - l + 1) + l;
        swap(nums, r, i);
        return partition(nums, l, r);
    }

    private int partition(int[] nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums, i, j);
            }
        }
        swap(nums, i + 1, r);
        return i + 1;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

 

使用默认的函数进行排序:

//时间7 ms击败71.34%
//内存41.9 MB击败84.9%
//时间复杂度:O(nlog⁡n),其中 n 是数组 arr 的长度。算法的时间复杂度即排序的时间复杂度。
//空间复杂度:O(log⁡n),排序所需额外的空间复杂度为 O(log⁡n)。
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        Arrays.sort(arr);
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec;
    }
}

 

posted @ 2023-02-11 16:14  忧愁的chafry  阅读(16)  评论(0编辑  收藏  举报