力扣-215. 数组中的第K个最大元素

快速选择算法,快速找到数组中第K个最大元素
感觉自己写,还是挺难了,看过题解,整理了一遍,给大家参考一些题解思路把
老菜鸡了,自己还琢磨了几个钟才理解。。

package Test.test;

/*
    @CreateTime 2021/11/1 10:30
    @CreateBy cfk
*/


import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        String[] s = sc.nextLine().split(" ");
        int[] arr = new int[s.length];
        for (int i = 0; i < s.length; i++) {
            arr[i] = Integer.parseInt(s[i]);
        }
        System.out.println(findKthLargest2(arr, 2));

    }

    //方法一
    //最容易想到的写法, 调用系统类库函数, 直接对数组进行排序
    //然后需要返回的是第k大的元素, 实际上就是第 (全部元素) - k 小的元素
    public static int findKthLargest1(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }

    //方法二
    //使用快速选择法
    //实际上我们可以通过任意排序算法对数组进行排序, 然后找到第 (全部元素) - k 小的元素
    //实现原理 同方法一
    //但是在快速排序的时候 我们可以减少排序的步骤
    //因为快速排序实际上就是不断找每个元素在数组中的位置,即左边的元素全部小于它,右边的元素全部大于它
    //在这个找的过程中,我们可以会直接找到(第 (全部元素) - k 小的元素){第k大的元素}在数组中的位置
    //这样我们就没必要继续向下判断了
    public static int findKthLargest2(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length-1, nums.length-k);
    }

    //寻找对应数
    public static int quickSelect(int[] nums, int left, int right, int targetIndex) {
        int index = randomPartition(nums, left, right);
        if (targetIndex == index) {
            return nums[index];
        } else {
            //如果没找到,则对基准数的左右按条件进行递归
            return index > targetIndex ? quickSelect(nums, left, index-1, targetIndex) :
                    quickSelect(nums, index+1, right, targetIndex);
        }
    }

    //此方法用于找到某个元素在有序数组中对应的位置
    public static int partition(int[] nums, int left, int right) {
        //左边初始化一个指针位置,方便后面进行换位
        int temp = left-1;
        //当左边运行到最右边就结束了 而不是整个数组
        for (int i = left; i < right; i++) {
            //如果当元素比右端基准位小时,都放在左端,每次换位的话让指针加一,如果没有发生换位指针位置不变
            if (nums[i] <= nums[right]) {
                swap(nums, i, ++temp); //这里要用++temp而不能用temp++,因为如果后加,在第一个元素时,temp为-1,会数组越界
            }
        }
        //最后把基准位和temp+1位更换位置,则基准位的左边全部是小于它的元素,右边是大于它的元素
        swap(nums, temp+1, right);
        return temp+1;
    }

    //随机抽取数组内数找到它对应数组中的位置,避免如果数组稍微有序的话,进行太多无用的操作,可以提高效率
    public static int randomPartition(int[] nums, int left, int right) {
        Random random = new Random();
        //right - left + 1 可以获取对应取间内所有元素的个数
        //random.nextInt(right - left + 1) 可以返回 0 到 元素总个数(不包括)
        //这里为什么要 +left???
        //因为这里要随机的是右边界元素,以它为基准继续排序,不是每次数组都是从第一位开始的
        // 如果不加left,做到的元素和期待的元素不对应
        int randomInt = random.nextInt(right - left + 1) + left;
        //交换 随机生成基准点
        swap(nums, randomInt, right);
        return partition(nums, left, right);
    }

    //交换两个元素的位置
    public static void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

}

// 作者:LeetCode-Solution
// 链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/solution/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcode-/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

补充

下面是构造大顶堆的方法实现

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int length = nums.length;
	      //创建大顶堆
        buildHeap(nums, length);
        //循环遍历,把因为题目要求的是第k大的元素,这里循环生成大顶堆,每次把最大的元素和最后一个元素//换位置,并且移除,这样没循环一次,第一个元素就是第K个最大元素
        for(int i = nums.length-1; i >= nums.length-k+1; i-- ) {
            swap(nums, 0, i);
            length--;
					//注意这里不需要再生成完全大顶堆了,因为此时的二叉树除了第i个元素其他的都已经是大顶堆了,//所以这里直接不断按第一个元素生成大顶堆就好了
            maxHeap(nums, 0, length);
            
        }
        return nums[0];
    }

    public void buildHeap(int[] nums, int length) {
				//循环生成大顶堆树,需要找到树的中间,然后不断递减,依次生成大顶堆,即可遍历整颗树
        for(int j = length/2; j >= 0; j--) {
            maxHeap(nums, j, length);
        }
    }
    public void maxHeap(int[] nums, int i, int length) {
				//l代表最左边子树(④)的左子节点,r为右子结点
				//用max保存i初始化最大值
        int l = 2*i+1, r = 2*i+2, max = i;
				//这里要注意判断 l < length 以及 r < length 同理
				//因为(④)可能并没有子节点
        if(l < length && nums[l] > nums[max]) {
            max = l;
        }
        if(r < length && nums[r] > nums[max]) {
            max = r;
        }
        if(max != i) {
		//如果max不为i,说明已经发生了改变,交换此时最大值,并且递归方法
		//因为当前面构成大顶堆时,元素的位置发生改变,此时结构又会发生改变
            swap(nums, i, max);
            maxHeap(nums, max, length);
        }
    }
    public void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}


其实构造大顶堆的方法挺多的,这里在给大家分享一个不需要递归的

public void maxHeap(int[] nums, int i, int length){
		//保存当前元素,最后要使用到
		int x = nums[i];
		//从i开始构建大顶堆,如果i下还有子树同样需要迭代构建大顶堆
		for(int j = 2*i+1; j < length; j = 2*j+1) {
            if(j+1 < length && nums[j] < nums[j+1]) {
                j++;
            }
            if(nums[j] > x){
                nums[i] = nums[j];
                nums[j] = x;
                i = j;
            }
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值