天天看点

【面试高频题】难度 1.5/5,多解法经典面试题

题目描述

这是 LeetCode 上的 ​​215. 数组中的第K个最大元素​​ ,难度为 中等。

Tag : 「树状数组」、「二分」、「优先队列(堆)」、「快速选择」

给定整数数组 ​

​nums​

​​ 和整数 ​

​k​

​​,请返回数组中第 ​

​k​

​ 个最大的元素。

请注意,你需要找的是数组排序后的第 ​

​k​

​​ 个最大的元素,而不是第 ​

​k​

​ 个不同的元素。

你必须设计并实现时间复杂度为

示例 1:

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

示例 2:

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

提示:

值域映射 + 树状数组 + 二分

除了直接对数组进行排序,取第 位的

对于值域大小 小于 数组长度本身时,我们还能使用「树状数组 + 二分」的 做法,其中

首先值域大小为 ,为了方便,我们为每个 增加大小为 的偏移量,将值域映射到

将每个增加偏移量后的 存入树状数组,考虑在 范围内进行二分,假设我们真实第 大的值为 ,那么在以

  • 在范围内的数满足「树状数组中大于等于的数不低于
  • 在范围内的数不满足「树状数组中大于等于的数不低于

二分出结果后再减去刚开始添加的偏移量即是答案。

代码:

class Solution {
    int M = 10010, N = 2 * M;
    int[] tr = new int[N];
    int lowbit(int {
        return x & -x;
    }
    int query(int {
        int ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
        return ans;
    }
    void add(int {
        for (int i = x; i < N; i += lowbit(i)) tr[i]++;
    }
    public int findKthLargest(int[] nums, int {
        for (int x : nums) add(x + M);
        int l = 0, r = N - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (query(N - 1) - query(mid - 1) >= k) l = mid;
            else r = mid - 1;
        }
        return      
  • 时间复杂度:将所有数字放入树状数组复杂度为;二分出答案复杂度为,其中为值域大小。整体复杂度为
  • 空间复杂度:

优先队列(堆)

另外一个容易想到的想法是利用优先队列(堆),由于题目要我们求的是第

根据当前队列元素个数或当前元素与栈顶元素的大小关系进行分情况讨论:

  • 当优先队列元素不足
  • 当优先队列元素达到

代码:

class Solution {
    public int findKthLargest(int[] nums, int {
        PriorityQueue<Integer> q = new PriorityQueue<>((a,b)->a-b);
        for (int x : nums) {
            if (q.size() < k || q.peek() < x) q.add(x);
            if (q.size() > k) q.poll();
        }
        return      
  • 时间复杂度:
  • 空间复杂度:

快速选择

对于给定数组,求解第

基本思路与「快速排序」一致,每次敲定一个基准值 ​

​x​

​​,根据当前与 ​

​x​

​​ 的大小关系,将范围在 的

同时利用,利用题目只要求输出第 大的值,而不需要对数组进行整体排序,我们只需要根据划分两边后,第

快速排序模板为面试向重点内容,需要重要掌握。

代码:

class Solution {
    int[] nums;
    int qselect(int l, int r, int {
        if (l == r) return nums[k];
        int x = nums[l], i = l - 1, j = r + 1;
        while (i < j) {
            do i++; while (nums[i] < x);
            do j--; while (nums[j] > x);
            if (i < j) swap(i, j);
        }
        if (k <= j) return qselect(l, j, k);
        else return qselect(j + 1, r, k);
    }
    void swap(int i, int {
        int c = nums[i];
        nums[i] = nums[j];
        nums[j] = c;
    }
    public int findKthLargest(int[] _nums, int {
        nums = _nums;
        int n = nums.length;
        return qselect(0, n - 1, n - k);
    }
}      
  • 时间复杂度:期望
  • 空间复杂度:忽略递归带来的额外空间开销,复杂度为

最后

这是我们「刷穿 LeetCode」系列文章的第 ​

​No.215​

​ 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。