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;
}
}