LeetCode 最小 K 个数/数组中的第 K 个最大元素[排序]

267 阅读2分钟

这是我参与更文挑战的第 9 天,活动详情查看: 更文挑战

最小 K 个数(面试题17.14)

题目描述

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

示例 1:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]

提示

  • 0 <= len(arr) <= 100000
  • 0 <= k <= min(100000, len(arr))

思路分析

想到找到最小 K 个数,最简单粗暴的方法可以对数组进行排序,然后直接找到最小 K 个数,比如我们用快速排序,时间复杂度是O(nlogn){O(nlogn)},但是我们可以利用快排的思想进一步优化,快排利用的是分治的思想,取一个值,大于该值的在左侧,小于该值的在右侧。找到最小 K 个数,可以转化为先找到第(length - K)大的数。

每次快排后,我们就可以缩小我们的区间,因为我们不必对数组进行排序,只需要找到最小的一部分的值,所以最终的时间复杂度是O(n){O(n)}

代码展示

解法一:时间复杂度是O(n){O(n)},空间复杂度是O(1){O(1)}

public int[] smallestK(int[] arr, int k) {
        if (arr.length == 0){
            return arr;
        }

        int m = arr.length  - k;
        int start = quickSort(arr,m);
        int[] nums = new int[k];
        int index = 0;
        for (int i = m;i < arr.length;i++){
            nums[index++] = arr[i];
        }
        return nums;
    }

    private int quickSort(int[] nums,int k){
        return quickInternal(nums,0,nums.length - 1,k);
    }

    private int quickInternal(int[] nums,int left,int right,int k){  //5,4,3,2,2,2,1,0
        int division = division(nums, left, right);
        if (division + 1 == k){
            return division;
        } else if(division + 1 > k){
            return quickInternal(nums,left,division - 1,k);
        } else {
            return quickInternal(nums,division + 1,right,k);
        }
    }

    private int division(int[] nums,int left,int right){
        int base = nums[left];
        while (left < right){
            while (left < right && nums[right] <= base){
                right--;
            }
            nums[left] = nums[right];

            while (left < right && nums[left] > base){
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = base;
        return left;
    }

数组中的第 K 个最大元素(215)

题目描述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

进阶

  • 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例 1:

输入: [3,2,1,5,6,4]k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6]k = 4
输出: 4

说明

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

思路分析

这道题和上面的题目很类似,想到找到数组中的第 K 个最大元素,最简单粗暴的方法可以对数组进行排序,然后直接找到第 K 个最大元素,比如我们用快速排序,时间复杂度是O(nlogn){O(nlogn)},但是我们可以利用快排的思想进一步优化,快排利用的是分治的思想,取一个值,大于该值的在左侧,小于该值的在右侧。

每次快排后,我们就可以缩小我们的区间,因为我们不必对数组进行排序,只需要找到最小的一部分的值,所以最终的时间复杂度是O(n){O(n)}

代码展示

解法一:时间复杂度是O(n){O(n)},空间复杂度是O(1){O(1)}

public int findKthLargest(int[] nums, int k) {
        return quickSort(nums,k);
    }

    private int quickSort(int[] nums,int k){  //3,2,1
        return quickInternal(nums, 0, nums.length - 1,k);
    }

    private int quickInternal(int[] nums,int left,int right,int k){
        int division = division(nums,left,right); //5,4,3,2,1       5
        if (division+1 == k){
            return nums[division];
        } else if (division+1 > k){
            return quickInternal(nums,left,division-1,k);
        } else {
            return quickInternal(nums,division+1,right,k);
        }

    }

    private int division(int[] nums,int left,int right){
        int base = nums[left];
        while (left < right){
            while (left < right && nums[right] <= base){
                right--;
            }
            nums[left] = nums[right];

            while (left < right && nums[left] > base){
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = base;
        return left;
    }

总结

对于这类的第 K 大/小元素,最大/小 K 个数,解题思路都是一样的,利用快排思想,省去了具体的详细排序,时间复杂度是O(n){O(n)},空间复杂度是O(1){O(1)}