Bootstrap

【LeetCode】215. 数组中的第K个最大元素(Java)

目录

题目描述

思路 1:sort 排序

思路 2:优先队列

Java PriorityQueue

创建PriorityQueue

将元素插入 PriorityQueue

访问 PriorityQueue 元素

删除 PriorityQueue 元素

遍历 PriorityQueue

PriorityQueue 其他方法

PriorityQueue 比较器 (comparator)

思路 3:快速排序

思路 4:快速选择排序


题目描述

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

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

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

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

示例 2:

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

提示:

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

思路 1:sort 排序

  • 时间复杂度 O(NlogN),空间复杂度 O(1)
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }

思路 2:优先队列

  • 时间复杂度 O(NlogK),空间复杂度 O(K)
  • 在优先队列中,元素将按升序从队列中移除
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue();
        for (int val : nums) {
            pq.add(val);
            if (pq.size() > k)  // 维护堆的大小为 K
                pq.poll();
        }
        return pq.peek();
    }

Java PriorityQueue

  • PriorityQueue 类提供堆数据结构的功能,它实现了 Queue接口
  • 与普通队列不同,优先队列元素是按排序顺序检索的,但其元素可能没有排序。
  • 假设我们想以升序检索元素。在这种情况下,优先队列的头是最小的元素。检索到该元素后,下一个最小的元素将成为队列的头。

创建PriorityQueue

    // 导入包    
    import java.util.PriorityQueue;    
    // 创建优先队列
    PriorityQueue<Integer> numbers = new PriorityQueue<>();

       在上述代码中,我们创建了一个没有任何参数的优先级队列。在这种情况下,优先级队列的头是队列中最小的元素,元素将按升序从队列中移除。但是,我们可以借助 Comparator 接口自定义元素的顺序。

将元素插入 PriorityQueue

  • add() - 将指定的元素插入队列。如果队列已满,则会引发异常。

  • offer() - 将指定的元素插入队列。如果队列已满,则返回false。

    // 创建优先队列
    PriorityQueue<Integer> numbers = new PriorityQueue<>();

    // 使用add()方法
    numbers.add(4);
    numbers.add(2);
    System.out.println("PriorityQueue: " + numbers);

    // 使用offer()方法
    numbers.offer(1);
    System.out.println("更新后的PriorityQueue: " + numbers);

    // 输出结果
    PriorityQueue: [2, 4]
    更新后的PriorityQueue: [1, 4, 2]

访问 PriorityQueue 元素

  • 要从优先级队列访问元素,我们可以使用peek()方法。此方法返回队列的头部。
    // 创建优先级队列
    PriorityQueue<Integer> numbers = new PriorityQueue<>();
    numbers.add(4);
    numbers.add(2);
    numbers.add(1);
    System.out.println("PriorityQueue: " + numbers);

    // 使用peek()方法
    int number = numbers.peek();
    System.out.println("访问元素: " + number);

    // 输出结果
    PriorityQueue: [1, 4, 2]
    访问元素: 1

删除 PriorityQueue 元素

  • remove() - 从队列中删除指定的元素。

  • poll() - 返回并删除队列的开头。

    // 创建优先队列
    PriorityQueue<Integer> numbers = new PriorityQueue<>();
    numbers.add(4);
    numbers.add(2);
    numbers.add(1);
    System.out.println("PriorityQueue: " + numbers);

    // 使用remove()方法
    boolean result = numbers.remove(2);
    System.out.println("元素2是否已删除? " + result);

    // 使用poll()方法
    int number = numbers.poll();
    System.out.println("使用poll()删除的元素: " + number);

    // 输出结果
    PriorityQueue: [1, 4, 2]
    元素2是否已删除? true
    使用poll()删除的元素: 1

遍历 PriorityQueue

  • 可以使用 iterator() 方法,需要导入 java.util.Iterator 包。
    // 创建优先级队列
    PriorityQueue<Integer> numbers = new PriorityQueue<>();
    numbers.add(4);
    numbers.add(2);
    numbers.add(1);
    System.out.print("使用iterator()遍历PriorityQueue : ");

    // 使用iterator()方法
    Iterator<Integer> iterate = numbers.iterator();
    while(iterate.hasNext()) {
        System.out.print(iterate.next());
        System.out.print(", ");
    }

    // 输出结果
    使用iterator()遍历PriorityQueue : 1, 4, 2,

PriorityQueue 其他方法

  • contains(element) - 在优先级队列中搜索指定的元素。如果找到该元素,则返回true,否则返回false。
  • size() - 返回优先级队列的长度。
  • toArray() - 将优先级队列转换为数组,并返回它。

PriorityQueue 比较器 (comparator)

  • 自定义元素检索顺序 - 我们需要创建自己的comparator类,它实现了Comparator接口。
    import java.util.PriorityQueue;
    import java.util.Comparator;
    class Main {
        public static void main(String[] args) {
 
            // 创建优先级队列
            PriorityQueue<Integer> numbers = new PriorityQueue<>(new CustomComparator());
            numbers.add(4);
            numbers.add(2);
            numbers.add(1);
            numbers.add(3);
            System.out.print("PriorityQueue: " + numbers);
        }
    }

    class CustomComparator implements Comparator<Integer> {

        @Override
        public int compare(Integer number1, Integer number2) {
            int value =  number1.compareTo(number2);
            // 元素以相反的顺序排序
            if (value > 0) {
                return -1;
            }
            else if (value < 0) {
                return 1;
            }
            else {
                return 0;
            }
        }
    }

    // 输出结果
    PriorityQueue: [4, 3, 1, 2]

思路 3:快速排序

  • 首先选定一个基准数 base(一般选择 nums[0]),初始化两个指针 i 和 j,分别指向数组的上界和下界;
  • 先从最右侧开始依次向左搜索,找到第一个小于 base 的数,再从最左侧位置开始依次向右搜索,找到第一个大于 base 的数,交换指针 i 和指针 j 所指向的元素的值;
  • 重复以上操作直至指针 i 和 j 指向的元素相同,此时交换 base 与该元素的值;
  • 分别递归处理数组中 base 左右两边的子数组。
  • 时间复杂度 O(NlogN),空间复杂度 O(1)
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length - 1;
        quickSort(nums, 0, len);
        return nums[len - k + 1];
    }

    public void quickSort(int[] nums, int low, int high) {
        if (low > high) return;
        int i = low, j = high;
        // 基准数
        int base = nums[low];
        while (i < j) {
            // 找到右边小于基准数的数字
            while (nums[j] >= base && i < j) {
                j--;
            }
            // 找到左边大于基准数的数字
            while (nums[i] <= base && i < j) {
                i++;
            }
            if (i < j) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
        nums[low] = nums[i];
        nums[i] = base;
        quickSort(nums, low, j - 1);
        quickSort(nums, j + 1, high);
    }

思路 4:快速选择排序

       快速排序算法基于快速排序的思想衍生,其可以使某些需要 O(NlogN) 时间复杂度的问题,在平均复杂度 O(N) 下完成。常见的例子是求数组中的第 k 小的数。

  • 首先选定一个基准数 base(一般选择 nums[0]),初始化两个指针 i 和 j,分别指向数组的上界和下界;
  • 将数组中小于 base 的值移到数组左端,其他移动到数组右端;
  • 计算 base 左端的数 (包括 base 自己) 有多少,记为 count;
  • 如果 count 正好为 k,则返回此时的 base,此值即为第 k 小的数;
  • 如果左端的数 count 大于 k,说明第 k 小的数在左端,因此只递归左边即可,同理,如果不在左端,只递归右边。
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        k = len - k;
        int i = 0, j = len - 1;
        while (i < j) {
            int count = quickSelect(nums, i, j);
            if (count == k) {
                break;
            } else if (count < k) {
                i = count + 1;
            } else {
                j = count - 1;
            }
        }
        return nums[k];
    }

    public int quickSelect(int[] nums, int low, int high) {
        int i = low, j = high;
        // 基准数
        int base = nums[low];
        while (i < j) {
            // 找到右边小于基准数的数字
            while (nums[j] >= base && i < j) {
                j--;
            }
            // 找到左边大于基准数的数字
            while (nums[i] <= base && i < j) {
                i++;
            }
            if (i < j) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
        nums[low] = nums[i];
        nums[i] = base;
        return j;
    }

;