* 面试题40:最小的k个数
* 题目:输入n个整数,找出其中最小的k个数。
* 例如,输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4.
* 思路:
* 方法一:最简单,将数组由大到小排序 前k个数就是最小的k个数 时间复杂度O(nlogn)
* 方法二:只有当允许修改输入的数组时才能使用,时间复杂度O(n),空间复杂度O(1) 数据不会乱序
* 根据No39中数组中超过一半的数字的partition的方法,
* 可以知道第k个元素的左边就是比k小的k个元素,所以根据这种思想可以得到与partition类似的算法
package Test;
import java.util.ArrayList;
public class No40GetLeastNumbers_Partition {
/*
* 面试题40:最小的k个数
* 题目:输入n个整数,找出其中最小的k个数。
* 例如,输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4.
*
*
* 思路:
* 方法一:最简单,将数组由大到小排序 前k个数就是最小的k个数 时间复杂度O(nlogn)
* 方法二:只有当允许修改输入的数组时才能使用,时间复杂度O(n)
* 根据No39中数组中超过一半的数字的partition的方法,
* 可以知道第k个元素的左边就是比k小的k个元素,所以根据这种思想可以得到与partition类似的算法
*
* 方法三:若不允许修改原数组时使用,适用于处理海量数据,时间复杂度O(nlogn) 空间复杂度O(1) 数据不会乱序
*
*
*
* */
public static void main(String[] args) {
// TODO Auto-generated method stub
No40GetLeastNumbers_Partition g = new No40GetLeastNumbers_Partition();
int[] array = {9,8,7,5,3,4,6,2};
int length = 8;
int k = 4;
ArrayList<Integer> result = g.GetLeastNumbers_Partition(array,k);
System.out.println("输出数组中最小的"+k+"位数:");
System.out.println(result);
}
private ArrayList<Integer> GetLeastNumbers_Partition(int[] array,int k) {
// TODO Auto-generated method stub
if(array == null || array.length == 0 || k <= 0 || k > array.length) {
return new ArrayList<Integer>();
}
ArrayList<Integer> list = new ArrayList<Integer>();
int low = 0;
int high = array.length - 1;
int p = partition(array,low,high);
while(p != k-1) {
if(p < k-1)
low = p+1;
else
high = p-1;
p = partition(array,low,high);
}
for(int i = 0; i < k; i ++) {
list.add(array[i]);
}
return list;
}
public int partition(int[] array, int low, int high) {
// TODO Auto-generated method stub
int val = array[low];
int i = low + 1;
int j = high;
while(i <= j) {
while(i <= high && array[i] < val)
i++;
while(j >= low && array[j] > val)
j--;
if(i > j)
break;
swap(array,i++,j--);
}
swap(array,low,j);
return j;
}
//交换元素
public void swap(int[] array,int indexA,int indexB) {
int t = array[indexA];
array[indexA] = array[indexB];
array[indexB] = t;
}
}
* 方法三:若不允许修改原数组时使用,适用于处理海量数据,时间复杂度O(nlogn)
* 1>创建一个大小为k的数据容器来存储最小的k个数
* 2>每次从长度为n的数组中读取一个元素,若容器已有数字个数小于k,则表示不满,则直接将读取的数字存入即可。
* 若容器已有数字个数等于k,表示满了,则继续3>
* 3>容器满了,需要左3件事情:(1)在容器中(k个数字中)找出最大数
* (2)有可能需要将容器中最大值删除
* (3)有可能需要向容器中插入一个新的数字
*
* 容器可以使用 堆排序(最大堆) 或者 使用 红黑树/优先队列 也可以完成该操作
* 堆排序的最大堆:时间复杂度O(nlogk) 空间复杂度O(k) 数据可能会乱序
* 该程序首先将前k个数存入数组,
* 然后对其进行堆排序,
* 若之后的数比堆顶的数字还小,
* 则将其删除 插入一个新的数字,对堆进行调整
package Test;
import java.util.ArrayList;
import java.util.Arrays;
public class No40GetLeastNumbers_Heap {
/*
* 面试题40:最小的k个数
* 题目:输入n个整数,找出其中最小的k个数。
* 例如,输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4.
*
*
* 思路:
* 方法一:最简单,将数组由大到小排序 前k个数就是最小的k个数 时间复杂度O(nlogn)
* 方法二:只有当允许修改输入的数组时才能使用,时间复杂度O(n)
* 根据No39中数组中超过一半的数字的partition的方法,
* 可以知道第k个元素的左边就是比k小的k个元素,所以根据这种思想可以得到与partition类似的算法
*
* 方法三:若不允许修改原数组时使用,适用于处理海量数据,时间复杂度O(nlogn)
* 1>创建一个大小为k的数据容器来存储最小的k个数
* 2>每次从长度为n的数组中读取一个元素,若容器已有数字个数小于k,则表示不满,则直接将读取的数字存入即可。
* 若容器已有数字个数等于k,表示满了,则继续3>
* 3>容器满了,需要左3件事情:(1)在容器中(k个数字中)找出最大数
* (2)有可能需要将容器中最大值删除
* (3)有可能需要向容器中插入一个新的数字
*
* 容器可以使用 堆排序(最大堆) 或者 使用 红黑树/优先队列 也可以完成该操作
* 堆排序的最大堆:时间复杂度O(nlogk) 空间复杂度O(k) 数据可能会乱序
* 该程序首先将前k个数存入数组,
* 然后对其进行堆排序,
* 若之后的数比堆顶的数字还小,
* 则将其删除 插入一个新的数字,对堆进行调整
*
*
*
* */
public static void main(String[] args) {
// TODO Auto-generated method stub
No40GetLeastNumbers_Heap g = new No40GetLeastNumbers_Heap();
int[] array = {9,8,7,5,3,4,6,2};
int length = 8;
int k = 4;
ArrayList<Integer> result = g.GetLeastNumbers_Heap(array,k);
System.out.println("输出数组中最小的"+k+"位数:");
System.out.println(result);
}
//最大堆 首先将数组中的前k个元素放到容器中
//构建最大堆
private ArrayList<Integer> GetLeastNumbers_Heap(int[] array,int k) {
// TODO Auto-generated method stub
ArrayList<Integer> list = new ArrayList<Integer>();
if(array == null || array.length == 0 || k <= 0 || k > array.length) {
return new ArrayList<Integer>();
}
//复制前k个元素 并将其放入kArray中
int[] kArray = Arrays.copyOfRange(array,0,k);
//构建最大堆
buildHeap(kArray);
//对原数组向后进行遍历,若有元素小于最大堆的堆顶元素,
//之前将其替换为堆顶元素,
//并将最大堆进行调整,调整为平衡的状态
for(int i = k;i < array.length;i++) {
if(array[i] < kArray[0]) {
kArray[0] = array[i];
maxHeap(kArray,0);
}
}
for(int i = kArray.length - 1;i >= 0;i--)
list.add(kArray[i]);
return list;
}
//构建最大堆 将一个数组看作一个存储最大堆的数据结构 中间位置作为最大堆的根节点
/*构建最大堆的过程: 就是在数组中找最大 然后替换掉其位置上的数字的guocheng
* 1>首先
*
* */
public void buildHeap(int[] kArray) {
// TODO Auto-generated method stub
//折半进行 最大堆的构建 因为i>length/2的节点没有子节点
//因为 要在根、左、右子树中寻找最大值 所以只要保证只对:有左右子树的节点进行判断 即可,因为判断后就能保证左右子树都比根节点小了 就不必再判断左右子树的大小了
for(int i = kArray.length/2 - 1; i >= 0;i--) {
//求出第i个节点的数值
maxHeap(kArray,i);
}
}
//maxHeap得到根、左子树、右子树中最大的值 存放到根结点处 返回最大值(左右子树+根中最大值)
//此算法会和左右子树中的所有数值进行对比,一直到叶子节点 都会比较
public void maxHeap(int[] kArray, int i) {
// TODO Auto-generated method stub
//左节点、右节点、最大节点的索引
int left = 2*i + 1;
int right = left + 1;
int maxIndex = 0;
//首先判断 左节点 根节点
if(left < kArray.length && kArray[left] > kArray[i]) {
maxIndex = left;
}
else
maxIndex = i;
//最后再和右节点进行对比
if(right < kArray.length && kArray[right] > kArray[maxIndex]) {
maxIndex = right;
}
//判断最大值是否改变 改变进行交换
if(maxIndex != i) {
swap(kArray,maxIndex,i);
maxHeap(kArray,maxIndex);//验证是否是最大的
}
}
//交换数值
public void swap(int[] array,int indexA,int indexB) {
int t = array[indexA];
array[indexA] = array[indexB];
array[indexB] = t;
}
}
* 容器可以使用 堆排序的二叉树 或者 使用 红黑树/优先队列 也可以完成该操作
* 红黑树:TreeSet进行操作 时间复杂度O(nlogk) 空间复杂度O(k) 数据不会乱序
* 优先队列:PriorityQueue进行操作 时间复杂度O(nlogk) 空间复杂度O(k) 数据不会乱序
package Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.TreeSet;
public class No40GetLeastNumbers_RedBlackTree {
/*
* 面试题40:最小的k个数
* 题目:输入n个整数,找出其中最小的k个数。
* 例如,输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4.
*
*
* 思路:
* 方法一:最简单,将数组由大到小排序 前k个数就是最小的k个数 时间复杂度O(nlogn)
* 方法二:只有当允许修改输入的数组时才能使用,时间复杂度O(n)
* 根据No39中数组中超过一半的数字的partition的方法,
* 可以知道第k个元素的左边就是比k小的k个元素,所以根据这种思想可以得到与partition类似的算法
*
* 方法三:若不允许修改原数组时使用,适用于处理海量数据,时间复杂度O(nlogn)
* 1>创建一个大小为k的数据容器来存储最小的k个数
* 2>每次从长度为n的数组中读取一个元素,若容器已有数字个数小于k,则表示不满,则直接将读取的数字存入即可。
* 若容器已有数字个数等于k,表示满了,则继续3>
* 3>容器满了,需要左3件事情:(1)在容器中(k个数字中)找出最大数
* (2)有可能需要将容器中最大值删除
* (3)有可能需要向容器中插入一个新的数字
*
* 容器可以使用 堆排序的二叉树 或者 使用 红黑树/优先队列 也可以完成该操作
* 红黑树:TreeSet进行操作 时间复杂度O(nlogk) 空间复杂度O(k) 数据不会乱序
*
*
*
* 优先队列:PriorityQueue进行操作 时间复杂度O(nlogk) 空间复杂度O(k) 数据不会乱序
*
*
* */
public static void main(String[] args) {
// TODO Auto-generated method stub
No40GetLeastNumbers_RedBlackTree g = new No40GetLeastNumbers_RedBlackTree();
int[] array = {9,8,7,5,3,4,6,2};
int length = 8;
int k = 4;
ArrayList<Integer> result1 = g.GetLeastNumbers_RedBlackTree(array,k);
ArrayList<Integer> result2 = g.GetLeastNumbers_PriorityQueue(array,k);
System.out.println("使用红黑树的方式,输出数组中最小的"+k+"位数:"+result1);
System.out.println("--------------------------------------------------------");
System.out.println("使用优先队列的方式,输出数组中最小的"+k+"位数:"+result2);
}
//使用优先队列的方式 PriorityQueue
public ArrayList<Integer> GetLeastNumbers_PriorityQueue(int[] array, int k) {
// TODO Auto-generated method stub
if(array == null || array.length == 0 || k <= 0 || k > array.length) {
return new ArrayList<Integer>();
}
//优先队列知识点:
//https://www.cnblogs.com/gnivor/p/4841191.html
//优先队列实际上就是堆,默认是最小堆 需要设置Collections.reverseOrder()变为zuidadui
Queue<Integer> queue = new PriorityQueue<Integer>(Collections.reverseOrder());
for(int i = 0;i < array.length;i++){
if(queue.size() < k)
queue.add(array[i]);
else if(queue.peek() > array[i]) {
// peek() 获取堆顶元素 最大堆中就是最大的数值 第0个位置
queue.poll();
queue.offer(array[i]); //向队列添加元素
}
}
//将最大堆反转回最小堆 其实也不必转换 因为题目 并没有要求要递增还是递减 ^-^
Queue<Integer> reverseQueue = new PriorityQueue<Integer>();
for(int i = 0; i < k;i++) {
reverseQueue.add(queue.poll());
}
return new ArrayList<>(reverseQueue);
}
//使用红黑树的方式 RedBlackTree
public ArrayList<Integer> GetLeastNumbers_RedBlackTree(int[] array, int k) {
// TODO Auto-generated method stub
if(array == null || array.length == 0 || k <= 0 || k > array.length) {
return new ArrayList<Integer>();
}
TreeSet<Integer> set = new TreeSet<Integer>();
for(int i = 0; i < array.length ;i++) {
if(set.size() < k)
set.add(array[i]);
else if(set.last() > array[i]) {//i比容器中的最大的元素小 需要删除容器中的值,然后插入新的数值
//set.last()返回set中最大的元素
set.pollLast(); //将最大的删除
set.add(array[i]); //将新的元素插入到set中
}
}
return new ArrayList<>(set);
}
}