每日五题-(4)topK专项

TopK

剑指Offer40.最小的k个数,昨天想好的今天就写这道题,不过得用多种方式写出来,所以也算写了五道题,哈哈哈哈。
在这里插入图片描述

方式一:普通快排

解析过程:

其实这道题一眼看下去,最直接的方法就是对整个区间进行排序,然后再去输出前k个元素就完成了。快排的时间复杂度是O(nlogn)。

那么我们来回顾一下快排?

整体思路就是选一个元素,将小的放在其左面,大的放在其右面,然后递归执行,直到每个区间有序,使得整个区间有序。最难的也就是这个partition过程了,具体讲解在我的博客排序算法里都有。

代码实现:

class solution {
     //方式一:最直接方法,先将整体排序,再输出前k个元素,直接使用快排
     public int[] getLeastNumbers(int[] arr, int k) {
         quickSort(arr,0,arr.length-1);
         int[] res = new int[k];
         for(int i = 0; i < k;i++){
             res[i] = arr[i];
         }
         return res;
     }

     public static void quickSort(int[] arr,int left,int right){
         if(left >= right)return;
         int i = left,j = right;
         while(i < j){
             while(i < j && arr[j] >= arr[left])j--;
             while(i < j && arr[i] <= arr[left])i++;
             swap(arr,i,j);
         }
         swap(arr,i,left);
         quickSort(arr,left,i-1);
         quickSort(arr,i+1,right);
     }
     public static void swap(int[] arr,int i,int j){
         int temp = arr[i];
         arr[i] = arr[j];
         arr[j] = temp;
     }
}

方式二:快排思想

解析过程:

有了上面的铺垫,那么方式二才算正式的解题。

因为我们只需要输出前k个元素,而无需对其进行排序,那么快排就是这样的过程啊。我们只要找到k对应的基准值(i),那么也就找到了前k个元素了。

  1. k == i,说明前k个元素都小于基准值,也就是topK了。
  2. k < i,说明前k个元素还在左 ;半个区域,那么就继续对左半部分快排。
  3. k > i,说明前k个元素在右半个区域,对右半部分进行快排。

这时候我们去分析时间复杂度,第一次是N,第二次

代码实现:

class solution{
	 public int[] getLeastNumbers(int[] arr, int k) {
         if(k >= arr.length)return arr;
         return quickSort(arr,0,arr.length-1,k);
     }
     public static int[] quickSort(int[] arr,int left,int right,int k){
         int i = left,j = right;
         while(i < j){
             while(i < j && arr[j] >= arr[left])j--;
             while(i < j && arr[i] <= arr[left])i++;
             swap(arr,i,j);
         }
         swap(arr,i,left);
         if(i > k)return quickSort(arr,left,i-1,k);
         if(i < k)return quickSort(arr,i+1,right,k);
         return Arrays.copyOf(arr,k);
     }
     public static void swap(int[] arr,int i,int j){
         int temp = arr[i];
         arr[i] = arr[j];
         arr[j] = temp;
     }
}

方式三:堆排

解析过程:

与之前第一种方式其实是一样的,不过这里就是换了排序算法而已,也是为第四种方式做铺垫。堆排的整体思路是建堆,这里我们是要找最小值,所以建大堆,然后再将根值依次放到无序区间最后一个位置,详细解析之前博客也有,也在排序算法博客中。

代码实现:

class solution{
     public int[] getLeastNumbers(int[] arr, int k) {
         if(k == 0)return new int[k];
         if(k >= arr.length) return arr;
         //1.建大堆,使得最大值在数组第一个位置
         createBigHeap(arr);
         //2.进行堆排
         for(int i = 0;i < arr.length-1;i++){
             int temp = arr[0];
             arr[0] = arr[arr.length-i-1];
             arr[arr.length-i-1] = temp;
             adJustDown(arr,0,arr.length-i-1);
         }

         //3.返回前k个数
         return Arrays.copyOf(arr,k);
     }

     //建大堆
     public static void createBigHeap(int[] arr){
         int lastIndex = arr.length-1;
         int lastPIndex = (lastIndex-1)/2;
         int size = arr.length;
         while(lastPIndex >= 0){
             adJustDown(arr,lastPIndex,size);
             lastPIndex--;
         }
     }
     //向下调整
     public static void adJustDown(int[] arr,int index,int size){
         int leftIndex = index*2+1;
         //1.判断当前位置是否为叶子节点
         while(leftIndex < size){
             //找子节点中最大的那个
             int maxIndex = leftIndex;
             int rightIndex = leftIndex + 1;
             if(rightIndex < size && arr[rightIndex] > arr[maxIndex]){
                 maxIndex = rightIndex;
             }
             //判断最大值是否大于根节点,如果大于等于则直接返回
             if(arr[index] >= arr[maxIndex])break;
             //到这里说明是小于,所以与子节点进行交换
             int temp = arr[index];
             arr[index] = arr[maxIndex];
             arr[maxIndex] = temp;

             //将index设置为maxIndex
             index = maxIndex;
             leftIndex = index*2 + 1;
         }
     }
}

方式四:堆排优化

解析过程:

在堆排的基础上,我们可以对其进行优化,同样的是针对输出前k个元素,对输出的顺序没有要求,那么我们就可以先将原数组的前k个元素单独建堆,然后这时候遍历原数组后面所有的元素,将它们与堆中最大元素进行比较,如果比最大元素小,则交换,对该元素进行向下调整即可。这样的话时间复杂度就优化了很多,为nlogk,注意这里k是一个常数。向下调整以及建大堆过程都是一样的。

代码实现:

class solution{
	public int[] getLeastNumbers(int[] arr, int k) {
        if(k == 0)return new int[k];
        if(k >= arr.length)return arr;

        //建一个含有k个元素的大根堆,也就是arr的前k个元素
        int[] res = new int[k];
        for(int i = 0;i < k;i++){
            res[i] = arr[i];
        }
        createBigHeap(res);
        for(int i = k;i < arr.length;i++){
            if(arr[i] < res[0]){
                res[0] = arr[i];
                adJustDown(res,0,k);
            }
        }
        return res;
    }
    //建大堆
    public static void createBigHeap(int[] arr){
        int lastIndex = arr.length-1;
        int lastPIndex = (lastIndex-1)/2;
        int size = arr.length;
        while(lastPIndex >= 0){
            adJustDown(arr,lastPIndex,size);
            lastPIndex--;
        }
    }
    //向下调整
    public static void adJustDown(int[] arr,int index,int size){
        int leftIndex = index*2+1;
        //1.判断当前位置是否为叶子节点
        while(leftIndex < size){
            //找子节点中最大的那个
            int maxIndex = leftIndex;
            int rightIndex = leftIndex + 1;
            if(rightIndex < size && arr[rightIndex] > arr[maxIndex]){
                maxIndex = rightIndex;
            }
            //判断最大值是否大于根节点,如果大于等于则直接返回
            if(arr[index] >= arr[maxIndex])break;
            //到这里说明是小于,所以与子节点进行交换
            int temp = arr[index];
            arr[index] = arr[maxIndex];
            arr[maxIndex] = temp;

            //将index设置为maxIndex
            index = maxIndex;
            leftIndex = index*2 + 1;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值