剑指Offer面试题(第二十五天)面试题40(Partition)、40(Heap) 、40(Red_BlackTree+PriorityQueue)

* 面试题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);
		
	}

	

}

猜你喜欢

转载自blog.csdn.net/weixin_43137176/article/details/89404625
今日推荐