《剑指offer》刷题——【时间效率】面试题40:最小的K个数(java实现)

《剑指offer》刷题——【时间效率】面试题40:最小的K个数(java实现)

  • 一、题目描述
  • 二、题目分析
    • 方法一:O(nlogn)
    • 方法二:基于Partition函数O(n)-允许修改输入的数组
    • 方法三:不修改数组-O(nlogk),适合处理海量数据

一、题目描述

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

二、题目分析

方法一:O(nlogn)

  • 把输入的n个整数排序
  • 最前面的K个数就是最小的k个数

方法二:基于Partition函数O(n)-允许修改输入的数组

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();//存最小的k个数
        //无效输入
        if(input==null || k>input.length || k<=0){
            return list;
        }
        
        int start = 0;//数组起始小标
        int end = input.length-1;//数组终止下标
        int index = Partition(input,start,end);//基准元素下标
        //若当前下标等于k-1,则数组中前k个数是最小k个数
        while(index !=k-1){
            //若当前下标大于k-1,则在左边查找
            if(index > k-1){
                end = index-1;
                index = Partition(input,start,end);
            }
            //若当前下标小于k-1,则在右边查找
            else{
                start = index+1;
                index = Partition(input,start,end);
            }
        }
        //将数组中前K个数添加到list中
        for(int i=0; i<k; i++){
            list.add(input[i]);
        }
        //返回list
        return list;
    }
    
    
    /**
    * 在基准元素左边的元素都小于基准元素,在基准元素右边的元素都大于等于基准元素。
    * 可以使用partition()方法解决以下常见的问题:
    *  1)查找数组中出现次数超过数组长度一半的元素。
    *   eg:MoreThanHalfSize类中的getElementOfMoreThanHalfSizeByPartition方法。
    *  2)查找数组中最小的(或最大的)k个数 或 查找数组中第k小(或第k大)的数
    *     优点:时间复杂度为O(n)
    *     缺点:改变了原数组中元素的位置。
    */
    public int Partition(int[] array, int start, int end){
        if(array==null || array.length<=0 || start<0 || end>=array.length){
            return -1;
        }
        
        int pivotIndex = start; //基准元素的索引
        //交换首尾元素
        swap(array, start, end);//基准元素放在末尾  
        int small = start-1;//基准元素的最终位置的下标
        //遍历,将小于基准的移到左边,大于的移到右边
        for(pivotIndex=start; pivotIndex<end; pivotIndex++){
            //小于基准元素
            if(array[pivotIndex] < array[end]){
                small++;
                //当当前遍历到的元素位置与基准最终位置指针不相等时,交换
                if(small != pivotIndex){
                //交换当前元素与基准的位置
                    swap(array, pivotIndex, small);
                }
            }
        }
        small++;
        //将基准元素放在最终的位置
        swap(array, small, end);
        //返回基准元素
        return small;
    }
    
    /**
    * 交换数组中两个元素
    */
    public void swap(int[] array, int pivotIndex, int curIndex){
        if(pivotIndex!=curIndex){
            int temp = array[pivotIndex];
            array[pivotIndex] = array[curIndex];
            array[curIndex] = temp;
        }
    }
}

方法三:不修改数组-O(nlogk),适合处理海量数据

  • 创建一个大小为k的容器,存储最小的k个数字,每次从输入的n个整数中读取一个数
  • 若容器中数字
  • 若容器中数字=k,则容器已满,不能插入,只能替换
    • 找出容器中k个数字的最大值
    • 比较这次待插入整数和最大值
      • 若待插 < 最大值,待插入的数字替换最大值
      • 若待插 > 最大值,舍弃此数
    • 容器满,有三个操作:1)在k个整数中找到最大数;2)可能在容器中删除最大数;3)可能要插入一个新数字
  • 二叉树实现
    • 最大堆:由于每次都需要找到k个整数的最大数字,故用最大堆;最大值获取需O(1),插入删除需O(logk)
    • 红黑树:红黑树通过把节点分为红黑两种颜色,并根据一些规则确保树上在一定程度上是平衡的,查找、删除、插入都需O(logk)

你可能感兴趣的:(剑指offer)