LeeCode 2022 面试题目学习回顾之一

115 阅读1分钟

字节面试题目:一个无序数组,找出第K大的元素 学习路径,参考labuladong

labuladong.github.io/algo/2/21/4…

快速排序的框架

    void sort(int[] nums, int lo, int hi){
      if(lo >= hi){
          return; 
      }
      //对nums[lo..hi] 进行切分
      //使得nums[lo..p-1] <= nums[p] < nums[p+1..hi]
      int p = partition(nums,lo,hi);
      //去左右子数组进行切分
      sort(nums, lo, p-1);
      sort(nums, p+1, hi);
      
}

基本思路

二叉堆的解法比较简单,实际写算法题的时候,推荐大家写这种解法。

可以把小顶堆 pq 理解成一个筛子,较大的元素会沉淀下去,较小的元素会浮上来;当堆大小超过 k 的时候,我们就删掉堆顶的元素,因为这些元素比较小,而我们想要的是前 k 个最大元素嘛。

当 nums 中的所有元素都过了一遍之后,筛子里面留下的就是最大的 k 个元素,而堆顶元素是堆中最小的元素,也就是「第 k 个最大的元素」。

二叉堆插入和删除的时间复杂度和堆中的元素个数有关,在这里我们堆的大小不会超过 k,所以插入和删除元素的复杂度是 O(logK),再套一层 for 循环,总的时间复杂度就是 O(NlogK)

当然,这道题可以有效率更高的解法叫「快速选择算法」,只需要 O(N) 的时间复杂度。

快速选择算法不用借助二叉堆结构,而是稍微改造了快速排序的算法思路,有兴趣的读者可以看详细题解。

解法代码

class Solution {
    public int findKthLargest(int[] nums, int k) {
        // 小顶堆,堆顶是最小元素
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int e : nums) {
            // 每个元素都要过一遍二叉堆
            pq.offer(e);
            // 堆中元素多于 k 个时,删除堆顶元素
            if (pq.size() > k) {
                pq.poll();
            }
        }
        // pq 中剩下的是 nums 中 k 个最大元素,
        // 堆顶是最小的那个,即第 k 个最大元素
        return pq.peek();
    }
}

使用快排和划分法实现

class Solution {
    public int findKthLargest(int[] nums, int k) {
        //首先随机打乱数组
        shuffle(nums);
        int lo = 0, hi = nums.length - 1;
        //转化成【排名第K的元素】
        k= nums.length - k;
        while(lo <= hi){
            //在nums[lo..hi]中选一个分界点
            int p = partition(nums, lo, hi);
            if(p< k){
                //第K大的元素在nums[p+1..hi]中
                lo = p + 1;
            }else if(p > k){
                //第K大的元素在nums[lo..p-1]中
                hi = p - 1;
            }else{
                //找到第K大元素
                return nums[p];
            }
        }
        return -1;
        
    }

    //洗牌算法,将输入的数组随机打乱
    void shuffle(int[] nums){
        Random rand = new Random();
        int n = nums.length;
        for(int i = 0; i < n; i++){
            //生成[i, n -1]的随机数
            int r = i + rand.nextInt(n - i);
            swap(nums, i, r);
        }
    }

    //对nums[lo...hi] 进行切分
    int partition(int[] nums, int lo, int hi){
        int pivot = nums[lo];
        int i = lo + 1,j = hi;
        while(i <= j){
            while(i < hi && nums[i] <= pivot){
                i++;
            }
            while(j > lo && nums[j] > pivot){
                j--;
            }
            if(i >= j){
                break;
            }
            swap(nums, i , j);
        }

        swap(nums, lo, j);
        return j;
    }

    //原地交换数组,将输入的数组随机打乱
    void swap(int[] nums, int i, int j ){
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
    }
}

明白了上面的原理,其实可以比较容易地实现快排算法

class Solution {
    public int findKthLargest(int[] nums, int k) {
        //首先随机打乱数组
        shuffle(nums);
        //排序整个数组(原地修改)
        sort(nums, 0, nums.length - 1);
        
    }

    //洗牌算法,将输入的数组随机打乱
    void shuffle(int[] nums){
        Random rand = new Random();
        int n = nums.length;
        for(int i = 0; i < n; i++){
            //生成[i, n -1]的随机数
            int r = i + rand.nextInt(n - i);
            swap(nums, i, r);
        }
    }

    void sort(int[] nums, int lo, int hi){
        if(lo >= hi){
            return;
        }
        int p = partition(nums, lo, hi);
        sort(nums, lo, p-1);
        sort(nums, p+1, hi);
    }

    //对nums[lo...hi] 进行切分
    int partition(int[] nums, int lo, int hi){
        int pivot = nums[lo];
        int i = lo + 1,j = hi;
        while(i <= j){
            while(i < hi && nums[i] <= pivot){
                i++;
            }
            while(j > lo && nums[j] > pivot){
                j--;
            }
            if(i >= j){
                break;
            }
            swap(nums, i , j);
        }

        swap(nums, lo, j);
        return j;
    }

    //原地交换数组,将输入的数组随机打乱
    void swap(int[] nums, int i, int j ){
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
    }
}