LeetCode 215. 数组中的第K个最大元素---Java(优先队列、快速排序)

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

示例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小的问题都可以通过快排和优先队列(最小堆、最大堆)来解决。

方法1使用快速排序进行分治
众所周知,每次快排都可以固定某个元素的位置,假设这个元素为pivot,下标为j,那么快排结束后在它左边的都是小于pivot的,在它右边都是大于pivot的。就有:

  • 区间[left, j-1]小于pivot

  • 区间[j+1,right]大于pivot

j就是第x大的元素下标
第K大元素下标为len-k(len为数组长度)

  • 如果j=len-K,表示j就是第K大的元素

  • 如果j>len-K,就表示当前元素找大了,表示第K大的元素在左区间[left, j-1],就去左部分区间快排查找

  • 如果j<len-K,就表示当前元素找小了,表示第K大的元素在区间[j+1,right],就去右部分区间快排查找

代码:

public class Solution {
    
    
    private static Random random = new Random(System.currentTimeMillis());

    public int findKthLargest(int[] nums, int k) {
    
    
        int len = nums.length;
        int left = 0;
        int right = len - 1;

        // 第k大元素的下标是len - k
        int target = len - k;

        while (true) {
    
    
        	//得到某元素的排好序固定位置的下标
            int index = partition(nums, left, right);
            if (index == target) {
    
    //说明找到了
                return nums[index];
            } else if (index < target) {
    
    //找小了,去右部分区间找
                left = index + 1;
            } else {
    
    //找大了,去左部分区间找
                right = index - 1;
            }
        }
    }

    public int partition(int[] nums, int left, int right) {
    
    
        // 在区间随机选择一个元素作为标定点
        if (right > left) {
    
    
            int randomIndex = left + 1 + random.nextInt(right - left);
            swap(nums, left, randomIndex);
        }

        int pivot = nums[left];

        // 将大于和小于pivot的元素分散到两边
        
        int lt = left + 1;
        int rt = right;

        while (true) {
    
    
        	//右移,直到找到一个不小于pivot的,作为交换元素
            while (lt <= rt && nums[lt] < pivot) {
    
    
                lt++;
            }
            //左移,直到找到一个不大于pivot的,作为交换元素
            while (lt <= rt && nums[rt] > pivot) {
    
    
                rt--;
            }
			//结束查询,此时rt下标指向了左部分区间的最右端,lt指向了右部分区间的最左端
            if (lt > rt) {
    
    
                break;
            }
            //进行交换,指针移动并继续查询
            swap(nums, lt, rt);
            lt++;
            rt--;
        }
		//固定pivot位置,此时[left,rt]区间都是小于pivot的
		//交换了以后,就有了[left,rt-1]、[rt]、[lt,right]三部分
		//rt就是这次快排后,pivot应该在的位置下标,返回
        swap(nums, left, rt);
        return rt;
    }

    private void swap(int[] nums, int index1, int index2) {
    
    
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

复杂度分析:
时间复杂度:O(n)。因为每次只操作一半的元素。所以就是N+N/2+N/4+…+1,和为O(n)
空间复杂度:O(1)

方法2最小堆
思路比较简单,维护一个有K个元素的最小堆
遍历数组,如果堆不满,直接添加;如果满了,则将遍历到的数x和堆顶元素比较(堆中最小元素),如果大于,弹出栈顶元素,并将x加入,否则不需要操作

代码:

public class Solution {
    
    
    public int findKthLargest(int[] nums, int k) {
    
    
        int len = nums.length;
        //默认最小堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        //先加入K个元素
        for (int i = 0; i < k; i++) {
    
    
            minHeap.add(nums[i]);
        }
        //遍历剩余元素
        for (int i = k; i < len; i++) {
    
    
            // 如果当前元素比栈顶元素大,弹出栈顶元素并加入num[i]
            if (nums[i] > minHeap.peek()) {
    
    
                minHeap.poll();
                minHeap.add(nums[i]);
            }
        }
        return minHeap.peek();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44759105/article/details/110677261