题目要求:O(n*logn)。求最小的k个数
第一种方法:最小堆,O(n*logk)版本。完全满足时间要求
import java.util.ArrayList;
public class Solution {
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 非递归调整方法
public static void adjustHeap(int[] array, int i, int length) {
// 先把当前元素取出来,因为当前元素可能要一直移动
int temp = array[i];
// 传进来当前结点编号i, k = 2*i+1 和 k+1 分别是他的左右孩子
// for循环的目的是:如果当前结点已经很靠上了,如果调整一下可能会影响下面的子树,所以要持续地往下检查是否满足要求
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { // 结点从i开始一个一个处理,直到
// 这个if语句的目的是判断左右孩子节点哪个大。k是左孩子,k+1是右孩子。如果右孩子大,就k++,代表k指向右子结点
if (k + 1 < length && array[k] > array[k + 1]) {
k++;
}
// 判断孩子节点中大较大者是否比父节点值要大。如果是,就交换,然后进入下一个for循环,检查子树是否还满足要求;如果不是,就break跳出函数,重新传入前一个非终端节点
if (array[k] < temp) { //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
swap(array, i, k); // 交换
i = k; // 为了配合下一个for循环堆其子树的检查,这里要更新一下当前位置
} else {
break;
}
}
//arr[i] = temp; // 这一句加不加都可以。
}
public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
// 建初堆
int len = input.length;
for (int i = len / 2 - 1; i >= 0; i--) {
adjustHeap(input, i, input.length); // i初始为len/2-1,表示它是最后一个非终端结点。从这里开始i--,一直往前分析
}
// 调整堆
for (int j = len - 1; j > len - 1-k; j--) {
list.add(input[0]);
swap(input, 0, j); // 元素交换,把大顶堆的根元素,放到数组的最后
adjustHeap(input, 0, j); // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。调整剩下的元素从新变成堆
}
return list;
}
public static void main(String[] args){
int[] arr = {4,5,1,6,2,7,3,8};
System.out.println(GetLeastNumbers_Solution(arr,4));
}
}
第一种方法(补充):巧妙地运用用最大堆求前k小的值,比O(n*logk)还要小一些。完全满足时间要求
import java.util.ArrayList;
public class Solution {
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void adjustHeap(int[] array, int i, int length) {
int temp = array[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
if (k + 1 < length && array[k] < array[k + 1]) {
k++;
}
if (array[k] > temp) {
swap(array, i, k);
i = k;
} else {
break;
}
}
}
public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
int[] arr = new int[k]; //用于放最小的k个数
for (int i = 0; i < k; i++)
arr[i] = input[i];//先放入前k个数
// 建立前k个无序元素的初堆
int len = arr.length;
for (int i = len / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, len); // i初始为len/2-1,表示它是最后一个非终端结点。从这里开始i--,一直往前分析
}
// 动态调整堆
for (int i = k; i < input.length; i++) {
// 此时arr[0]是前k个无序元素的最大者。然后比较原无序数组里前k个元素后面是否还有比这个最大值小的,如果有,就替换掉,再重新调整堆
if (input[i] < arr[0]) { //存在更小的数字时
arr[0] = input[i]; // 替换
adjustHeap(arr, 0, k - 1);//重新调整最大堆。因为数组的末端是已经求得的最小值,不能再动了,所以每次要k-1
}
}
// 最后arr中剩下的就是原无序数组里前k小的值
for (int i : arr)
list.add(i);
return list;
}
public static void main(String[] args) {
int[] arr = {4, 5, 1, 6, 2, 7, 3, 8};
System.out.println(GetLeastNumbers_Solution(arr, 4));
}
}
第二种方法:快速排序,时间复杂度为O(nlogn)。刚好满足时间要求。 如果使用Partition函数,回达到O(n),完全符合要求
第三种方法:冒泡排序,O(n*k),不满足时间要求。但是也可以选出前k小的数(外层循环次数设为k)
static void bubbleSort1() {
for (int i = 0; i <k; i++) {
for (int j = 0; j < len-i-1; j++) {
if (arr[j] < arr[j + 1]) {
swap(arr, j, j + 1); //将数组中的两个数交换
}
}
}
}