题40:最小的k个数

我GitHub上的:剑指offer题解

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

思路1:把输入的n个整数进行排序,排序之后取前面的k个数。时间复杂度O(nlogn)

思路2:基于partition函数。如果基于数组的第k个数字来调整,则使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的前k个数字就是我们要求的数字(但这几个数字不一定是排序的),时间复杂度O(n),但是只有当我们可以修改数组时可以用

要熟悉partition函数的写法。

package basic;

import java.util.ArrayList;

public class Partition {

	public static int partition(int[] arr, int start, int end) {
		int key = arr[start];
		int i = start;
		int j = end;
		while(i < j) {
			// 从右往左找到比key小的数字
			while(i < j && arr[j] >= key) {
				j--;
			}
			if (i < j) {
				swap(arr, i++, j);
				
			}
			while(i < j && arr[i] <= key) {
				i++;
			}
			if (i < j) {
				swap(arr, i, j--);
			}
		}
		arr[i] = key;
		return i;
	}
	
	public static void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	public static ArrayList GetLeastNumbers_Solution(int [] input, int k) {
		ArrayList result = new ArrayList<>();
		if (input == null || k <= 0 || k > input.length) {
			return result;
		}
		
		int start = 0;
		int end = input.length - 1;
		int index = partition(input, start, end);
		while(index != k - 1) {
			if (index < k - 1) {
				index = partition(input, index + 1, end);
			}
			else {
				index = partition(input, start, index - 1);
			}
		}
		for(int i = 0 ; i <= index; i++) {
			result.add(input[i]);
		}
		return result;
		
	}
	
	public static void main(String[] args) {
		int[] arr = {8,1,4,9,11,4,13,55,3};
		ArrayList result = GetLeastNumbers_Solution(arr, 9);
		for (Integer integer : result) {
			System.out.print(integer + " ");
		}
	}
}

思路3:利用堆排序,建立大顶堆,将前K个数插入到堆中,然后从读入下一个元素,与堆顶元素进行比较。如果比堆顶元素大,就抛弃这个数,继续读下一个数,如果较小,就删除堆顶元素,插入这个数。

 

时间复杂度O(nlogk)

package shuti;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;


/**
 *  输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
 * @author yajie
 *
 */
public class GetLeastNumbers_Solution40 {
	ArrayList res = new ArrayList<>(); 
	public  ArrayList GetLeastNumbers_Solution(int [] input, int k) {
		// 最小的k个数,适合用堆
		// 用大顶堆,保存k个较小的数,最顶端是k个数里面最大的。
		// 新增加的要先和堆顶比较,如果比堆顶大,就不用加入,如果比堆顶小,就删除堆顶。
		// java中的优先队列PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆
		ArrayList result = new ArrayList<>();
		int length = input.length;
		if (k > length || k == 0) {
			return result;
		}
		
		PriorityQueue maxHeap = new PriorityQueue(
				k, new Comparator() {
					// 调整为大顶堆
					@Override
					public int compare(Integer o1, Integer o2) {
						return o2.compareTo(o1);
					}
				});
		
		
		for (int i = 0; i < length; i++) {
			if (maxHeap.size() < k) {
				maxHeap.offer(input[i]);
			}else if (maxHeap.peek() > input[i]) {
				// 堆顶大于input[i],删除堆顶的元素
				maxHeap.remove();
				maxHeap.offer(input[i]);
			}
		}
		

		while(!maxHeap.isEmpty()) {
			result.add(maxHeap.poll());
		}
		return result;
		
	}
}

思路三的方法有两个明显的优点:

1.没有修改输入的数据

2.适合海量数据的输入。假设题目的要求是从海量数据中找出最小的k个数字,由于内存的大小是有限的,有可能不能把这些海量的数据一次性全部载入内存。这时候,我们可以从辅助存储空间(如硬盘)中每次读入一个数字,根据上述的方法判断需不需要放入容器中。这种思路只要求内存能够容纳k个数就好了。因此它最适合的情形就是n很大,并且k较小的问题。

 

题40:最小的k个数_第1张图片

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