zoukankan      html  css  js  c++  java
  • 剑指29.最小的K个数

    题目描述

    输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
     

    思路

    思路1:利用排序。排序后最前面的K个数就是最小的K个数。                                                                       (时间复杂度O(nlogn))

    思路2:快排Partition思想(Partition函数既是快排的基础,也可以用来查找n个数中第k大的数)                        (时间复杂度为O(n),缺点是需要修改输入的数组)

            基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。调整之后,位于数组左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。这种思路虽时间复杂度最优,但会修改输入数组。

    思路3:最大堆。用最大堆保存k个数,每次只和堆顶比,如果比堆顶小,删除堆顶,新数入堆。                 (时间复杂度O(nlogk),特别适合处理从海量数据中找出最小的k个数)

           先创建一个大小为k的容器,如果容器中已有的数字小于k个,则直接把这次读入的数据放入容器中;如果容器中已有k个数了,我们找出这已有k个数中的最大值,如果接下来遍历的数值比容器中的最大值小,那么用这个数替换容器中的最大值。可以看出,容器满了之后需要做3件事。1)在k个数中找到最大数;2)有可能在容器中删除最大数;3)有可能要插入一个新数字。当需要在某个数据容器内频繁查找及替换最大值时,我们可以使用最大堆,最大堆中根节点的值总是大于它子树中任意节点的值。每次可以在O(1)的时间内得到已有k个数字的最大值,但需要O(logk)时间完成删除以及插入操作。

     

    ☆解法2(Partition思想)

    import java.util.ArrayList;
    public class Solution {
        // 采用快排的partition思想
        public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
            ArrayList<Integer> res = new ArrayList<>();
            if(input == null || k < 1 || k > input.length)
                return res;
            // 在数组中寻找位置为k - 1 的pivot
            int start = 0, end = input.length - 1;
            int index = partition(input,start,end);// 任取一个数字为基准,如果index=k-1,说明左边有k个数了
            while(index != k - 1){
                if(index < k - 1) 
                    start = index + 1;  // 说明左边不够k个数,因此对右边进一步划分
                else 
                    end = index - 1;   // 说明左边大于k个数,因此对左边进一步划分
                index = partition(input,start,end);
            }
            for(int i = 0; i < k; i++)
                res.add(input[i]);
            return res;
        }
        // partition版本2:进阶版,相对版本1优化不必要的交换
        private int partition(int[] arr, int start, int end){
            int pivot = arr[start];
            while(start < end){
                // 顺序很重要,要先从右往左找,如果右边的数比标准数大
                while(start < end && arr[end] >= pivot)
                    end--;
                arr[start] = arr[end]; // 采用替换而不是交换的方式进行操作,使用右边的数字替换左边的数
                // 再从左往右找,如果左边的数比标准数小
                while(start < end && arr[start] <= pivot)
                    start++;
                arr[end] = arr[start];  //使用左边的数字替换右边的数
            }
            // 此时,low和high重合了,标准数归位。
            arr[start] = pivot;
            return start;   //返回枢纽所在位置
        }
        
        // partition版本1 :需要交换数据
        // 任取一个数字为基准,进行比较和划分。 返回基准点的index
        private int partition(int[] arr, int start, int end){
            int pivot = arr[start];
            while(start < end){
                while(start < end && arr[end] >= pivot)  // 需要加上等号,大于等于pivot才能通过
                    end--;
                swap(arr,start,end);  //交换位置,使得大于pivot的值位于数组右边
                while(start < end && arr[start] <= pivot)  // 需要加上等号,小于等于pivot才能通过
                    start++;
                swap(arr,start,end);  //交换位置,使得小于pivot的值位于数组左边
            }
            return start;
        }
        private void swap(int[] arr, int i, int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    Note:举个例子理解一下上述过程~        参考:lron欣

    例如输入k=4,数组为(9,5,1,3,8,7,13,11,12)。以9为基准划分后,为(5,1,3,8,7,9,13,11,12),左边小于9的有5个数(9的索引不为3),仍需划分,再对(5,1,3,8,7)进行划分此时以5为划分,则形成(1,3,5,8,7),此时左边小5的只有2个(5的索引不为3),因此对右边进行划分,右边划分(7,8)。此时拿到的值为7的索引,整体上来说,原数组变成了(1,3,5,7,8,9,13,11,12)而7这个地方的索引是3,也就是说,左边的4个较小的数已经拿到了。

     

    解法3(最大堆)

    import java.util.*;
    public class Solution {
        // 用优先队列构造最大堆
        public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
            ArrayList<Integer> res = new ArrayList<>();
            if (input == null || k < 1 || k > input.length)
                return res;
            //PriorityQueue<Integer> maxHeap = new PriorityQueue<>(((o1, o2) -> o2.compareTo(o1))); //o1.compareTo(o2) 从小到大;反过来 从大到小,因此这里建立的是最大堆。
            PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
            for (int num : input){
                if (maxHeap.size() < k){
                    maxHeap.add(num);
                }else{
                    if (maxHeap.peek() > num){
                        maxHeap.poll();
                        maxHeap.add(num);
                    }
                }
            }
            while (!maxHeap.isEmpty())
                res.add(maxHeap.poll());
            return res;
        }
    }

    Note:以后复习时手写一个最大堆进行动态调整,,不使用优先队列。

     

    参考:

  • 相关阅读:
    Ruby on Rails中的Rake教程(Rake如何把我灌醉!)
    十一、Spring Boot 集成Shiro和CAS
    Spring Boot 快速入门
    一位创业者的血泪史----献给85前创业者的反思
    罗振宇送给新员工的四句话
    Axure 入门
    XMind常用快捷方式汇总
    CAS 单点登陆
    mysql互换表中两列数据
    mysql默认安装目录说明
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/13510655.html
Copyright © 2011-2022 走看看