剑指offer面试题40(java版):最小的K个数

welcome to my blog

剑指offer面试题40(java版):最小的K个数

题目描述

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

第三次做; 核心: 1)分治思想, 运行速度真的快! 2)parition之后返回pivot的索引, 比如说最终放到索引i处, 那么[0,i]这i+1个元素就是前i+1小的元素, 所以要找到i+1==k的情况
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int n = arr.length;
        if(k>n){
            return new int[]{};
        }
        quickSort(arr, 0, n-1, k);
        int[] res = Arrays.copyOf(arr, k);
        return res;
    }
    private void quickSort(int[] arr, int L, int R, int k){
        if(L>=R){
            return ;
        }
        int p = partition(arr, L, R);
        if(p+1 == k){
            return;
        }else if(p+1 < k){
            quickSort(arr, p+1, R, k);
        }else{
            quickSort(arr, L, p-1, k);
        }

    }

    private int partition(int[] arr, int L, int R){
        int small = L-1, big = R, p = L;
        while(p<big){
            if(arr[p] <= arr[R]){
                swap(arr, ++small, p++);
            }else{
                swap(arr, --big, p);
            }
        }
        swap(arr, p, R);
        return p;
    }

    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}
/*
使用partition, 一次partition过后会有一个元素被放到正确的位置上,
比如说最终放到索引i处,那么[0,i]这i+1个元素就是前i+1小的元素
我之前的使用方式, 并不是分支思想, 因为处理边界中要么有0, 有么要么有n-1, 并没有像分支算法那样缩小了处理空间
*/
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        //input check
        if(k==0){
            return new int[]{};
        }
        //
        partitionArr(arr, 0, arr.length-1, k);
        int[] res = new int[k];
        for(int i=0; i<k; i++){
            res[i] = arr[i];
        }
        return res;

    }

    private void partitionArr(int[] arr, int left, int right, int k){
        int index = partition(arr, left, right);
        if(index+1 == k){
            return;
        }
        else if(index+1 > k ){
            partitionArr(arr, left, index-1, k);
        }
        else{
            partitionArr(arr, index+1, right, k);
        }

    }

    private int partition(int[] arr, int left, int right){
        int small = left-1, big = right, pivot = arr[right], p = left;
        while(p<big){
            if(arr[p] <= pivot){
                swap(arr, ++small, p++);
            }else{
                swap(arr, --big, p);
            }
        }
        swap(arr, p, right);
        return p;
    }

    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}
第三次做,本来想用分支思想, 但是这里并不是分支思想!!! 这个方法太慢了, 不可取!!! 留作教训!! 巩固partition; 小于区的初始值是left-1; 核心: index+1表示[0,index]范围上元素的个数; index的可能取值是[0,n-1], 所以index+1的取值范围是[1,n], 覆盖了k的取值范围[1,n]
import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(k==0)
            return res;
        if(input==null || input.length==0)
            return res;
        if(input.length < k)
            return res;
        //
        int index = partition(input, 0, input.length-1);
        while(index != k-1){
            if(index > k-1)
                index = partition(input, 0, index-1);
            if(index < k-1)
                index = partition(input, index+1, input.length-1);
        }
        for(int i=0; i<=index; i++)
            res.add(input[i]);
        return res;
    }
    public int partition(int[] arr, int left, int right){
        int random = (int)(Math.random()*(right - left + 1)) + left;
        swap(arr, random, right);
        int small = left-1;//小于区 (最开始写错了小于区的初始值!)
        int big  = right;//大于区
        while(left < big){
            if(arr[left] < arr[right])
                swap(arr, left++, ++small);
            else if(arr[left] > arr[right])
                swap(arr, left, --big);
            else
                left++;
        }
        swap(arr, big, right);
        return big;
    }
    public void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
第三次做, 维护一个大根堆; 注意语法细节 new PriorityQueue<>(k, new Comparator(){…});
import java.util.PriorityQueue;
import java.util.ArrayList;
import java.util.Comparator;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(k==0)
            return res;
        if(input==null || input.length==0)
            return res;
        if(input.length<k)
            return res;
        
        PriorityQueue<Integer> pq = new PriorityQueue<>(k, new Comparator<Integer>(){
            public int compare(Integer o1, Integer o2){
                return o2 - o1;
            }
        });
        for(int i=0; i<input.length; i++){
            if(pq.isEmpty() || pq.size()<k)
                pq.add(input[i]);
            else{
                if(input[i]<pq.peek()){
                    pq.poll();
                    pq.add(input[i]);
                }
            }
        }
        while(!pq.isEmpty()){
            res.add(pq.poll());
        }
        return res;
    }
}
第二次做,使用partition,主要是注意k和索引的关系,如k==9时,返回的索引是8就可以; 和划分成小于区,等于区,大于区这种方法相比,将快排划分成小于等于区和大于区后代码有改变,比如while循环条件变了,以及不再有大于区的具体表示了,而是用small+1表示大于区的边界; 时间复杂度O(NlogN), 空间复杂度O(1)
import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(input == null || input.length==0 || input.length<k || k<1)
            return res;
        //使用partition,让partition返回小于等于区的边界
        int index = partition(input, 0, input.length-1);
        while(index != k-1){
            if(index < k-1)
                index = partition(input, index+1, input.length-1);
            else // index > k-1
                index = partition(input, 0, index-1);
        }
        //here, index == k
        for(int i=0; i<k; i++)
            res.add(input[i]);
        return res;
    }
    /*
    进行一次划分后,某个元素处于最终的位置上,该位置的左边都小于等于划分值,右边都大于划分值
    */
    public int partition(int[] arr, int left, int right){
        int randomIndex = (int)(Math.random()*(right-left+1)+left);
        swap(arr, randomIndex, right);//把划分值放到最后
        int small = left-1; //小于等于区 
        while(left < right){ //分成两个区:注意循环终止条件
            if(arr[left] <= arr[right])
                swap(arr, left++, ++small);
            else
                left++;
        }
        swap(arr, small+1, right);//将划分值放到大于区边界
        return small+1;//返回划分值的最终索引
    }
    public void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
第二次做, 进行k次选择排序,时间复杂度O(kN),空间复杂度O(1)
  • 这里不能用k次插入排序,因为k次插入排序后的排序的元素不一定处于最终位置,只有完整的进行插入排序后才可以,所以这里用选择排序不错
import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(input==null || input.length<k || k<1)
            return res;
        //selection sort
        int min;
        for(int i=0; i<k; i++){
            min = i;
            for(int j=i+1; j<input.length; j++){
                if(input[j] < input[min])
                    min = j;
            }
            swap(input, i, min);
            res.add(input[i]);
        }
        return res;
    }
    public void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
基于partition的方法, 会改变输入的数组
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        // 基于partition的方法
        //input check
        ArrayList<Integer> al = new ArrayList<Integer>();
        if(input.length < k)
            return al;
        //execute
        // partition的返回值为0,1,2,...N-1中的一个,返回1表示index为1,index是1的元素坐标有一个元素,所以直接用partition的返回值作为k的话,会有一个问题,就是k是N的时候,需要partition的返回值也是N,但是partition的返回值最大是N-1,此时就会造成partition数组越界
        if(k == input.length){
            for(int i=0; i<input.length; i++)
                al.add(input[i]);
            return al;
        }
        int index = partition(input, 0, input.length-1);
        while(index != k){
            if(index > k)
                index = partition(input, 0, index-1);
            if(index < k)
                index = partition(input, index+1, input.length-1);
        }
        for(int i=0; i<index; i++)
            al.add(input[i]);
        return al;
    }
    public int partition(int[] input, int lo, int hi){
        //注意input只有一个元素的情况, 此时要注意数组索引是否越界
        int i=lo, j=hi+1, pivot=input[lo];
        while(true){
            while(++i <= input.length && input[i] < pivot)
                if(i==hi)
                    break;
            while(--j >= 0 && input[j] >= pivot)
                if(j==lo)
                    break;
            if(i>=j)
                break;
            swap(input, i, j);
        }
        swap(input, lo, j);
        return j;
    }
    public void swap(int[] input, int i, int j){
        int temp = input[i];
        input[i] = input[j];
        input[j] = temp;
    }
}
基于最大堆的算法, 适用于海量数据, 不改变数据本身

优先队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值