剑指offer-最小的K个数

最小的K个数

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

输入

int[] input:待搜索的数组 ;
int k:需要找出最小的数量

输出

当k大于input长度时,返回空(非null);当k小于input长度时,返回input中最小的k个数字

  • 思路

    • 最粗暴方式,对数组所有元素排序(快排)
    • 利用冒泡排序的思想,冒泡k个元素
    • 划分思想,快排,每一次划分就会有一个数字位于以数组从小到达排列的的最终位置index
    • 最大堆(TreeSet)
  • 对所有元素进行排序

package com.genge.offer;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Created by Genge on 2016-06-27.
 */
public class GetLeastNumbers_Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if (input == null || input.length  <k) {
            return result;
        }
        Arrays.sort(input);//快速排序
        for (int i =0;i<k;i++) {
            result.add(input[i]);
        }
        return result;
    }
}
  • 冒泡思想(或者选择排序)
package com.genge.offer;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Created by Genge on 2016-06-27.
 */
public class GetLeastNumbers_Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if (input == null || input.length  <k) {
            return result;
        }
       /* Arrays.sort(input);
        for (int i =0;i<k;i++) {
            result.add(input[i]);
        }*/
        for (int i = 0 ;i<k;i++) {
            int m  = i ;
            for (int j =i+1;j<input.length;j++) {
                if (input[j] < input[m]) {
                    m = j;
                }
            }
            int temp = input[i];
            input[i] = input[m];
            input[m] = temp;
            result.add(input[i]);
        }
        return result;
    }
}
  • 划分思想

    • 利用快速排序划分的思想,每一次划分就会有一个数字位于以数组从小到达排列的的最终位置index;
    • 位于index左边的数字都小于index对应的值,右边都大于index指向的值;
    • 所以,当index>k-1时,表示k个最小数字一定在index的左边,此时,只需要对index的左边进行划分即可;
    • 当index < k - 1时,说明index及index左边数字还没能满足k个数字,需要继续对k右边进行划分;

代码如下:

package com.genge.offer;

import java.util.ArrayList;

/**
 * Created by Genge on 2016-06-27.
 */
public class GetLeastNumbers_Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if (input == null || input.length  <k) {
            return result;
        }
        int low = 0;
        int high = input.length - 1;
        int index = partition(input, low, high);
        while (index != k - 1) {
            if (index > k - 1) {
                high = index - 1;
            } else {
                low = index + 1;
            }
            index = partition(input, low, high);
        }
        for(int i = 0; i < k; i++){
            result.add(input[i]);
        }
        return result;
    }
    //划分操作
    private int partition(int[] input, int start, int end) {
        int pivot = input[start];
        while (start < end) {
            while (start < end && input[end] >= pivot) {
                end--;
            }
            input[start] = input[end];
            while (start < end && input[start] <= pivot) {
                start++;
            }
            input[end] = input[start];
        }
        input[start] = pivot;
        return start;
    }
}

这个算法虽然有代表性,但是居然不给通过,复杂度有点高啊:
运行超时:您的程序未能在规定时间内运行结束,请检查是否循环有错或算法复杂度过大。

  • 最大堆(TreeSet)

    • 可以先创建一个大小为k的数据容器来存储最小的k个数字,从输入的n个整数中一个一个读入放入该容器中,如果容器中的数字少于k个,按题目要求直接返回空;
    • 如果容器中已有k个数字,而数组中还有值未加入,此时就不能直接插入了,而需要替换容器中的值。按以下步骤进行插入:
      1. 先找到容器中的最大值;
      2. 将待查入值和最大值比较,如果待查入值大于容器中的最大值,则直接舍弃这个待查入值即可;
      3. 如果待查入值小于容器中的最小值,则用这个待查入值替换掉容器中的最大值;
      4. 重复上述步骤,容器中最后就是整个数组的最小k个数字。

对于这个容器的实现,我们可以使用最大堆的数据结构,最大堆中,根节点的值大于它的子树中的任意节点值。Java中的TreeSet类实现了红黑树的功能,它底层是通过TreeMap实现的,TreeSet中的数据会按照插入数据自动升序排列(按自然顺序)。因此我们直接将数据依次放入到TreeSet中,数组就会自动排序。

package com.genge.offer;

import java.util.ArrayList;
import java.util.TreeSet;

/**
 * Created by Genge on 2016-06-27.
 */
public class GetLeastNumbers_Solution {
    //最大堆
    public static ArrayList<Integer> GetLeastNumbers_Solution3(int [] input, int k) {
        if(input == null)
            return null;
        ArrayList<Integer> list = new ArrayList<Integer>(k);
        if(k > input.length)
            return list;
        TreeSet<Integer> tree = new TreeSet<Integer>();
        for(int i = 0 ; i < input.length; i++){
            tree.add(input[i]);
        }
        int i = 0;
        for(Integer elem : tree){
            if(i >= k)
                break;
            list.add(elem);
            i++;
        }
        return list;
    }
}

时间复杂度为O(nlogn),优点:
1. 不会改变原来数组;
2. 这种思想,适合处理海量数据,特别是n大k小的情况。在处理海量数据的时候,受内存限制,数据可能不能一次全部读入内存,此时用这种方式也很好处理,只要想每次读入一些数据,与我们的容器中最大值比较,看是否需要进行替换操作。

缺点就是:
TreeSet不允许重复数据,因为TreeSet的底层是TreeMap实现,是将TreeSet添加的内容作为TreeMap的key值来存储,也就不能存在重复数据。由于这种限制,这就对我们的输入数组有要求,但我们可以通过自己实现最大堆或优化TreeSet来实现兼容存在重复数字的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值