Day29:最小的K个数

时间:2022-07-24
本文章向大家介绍Day29:最小的K个数,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

剑指Offer_编程题——最小的K个数

题目描述:

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

具体要求:

时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M

具体思路:

思路   我们看题意可知,今天的题目上一道题比较的类似,上一道题是将数组中重复数字次数超过数组长度一半输出来这个数字。而今天的的数字是输出最小K个数。都是要寻找第k个位置的数,因此这个过程我们可以借鉴上文中的思路四的方法。并且查找这个数字的过程中时间复杂度为O(n),并且可以得知左边的数字比这个数小,右边的数字比这个数大。因此当K=4时,直接输出前K个数字,不过最后还要对输出的这K个数字进行排序。具体我们用python将其实现。

class Solution:
	def PartitionOfK(self, numbers, start, end, k):
		if k < 0 or numbers == [] or start < 0 or end >= len(numbers) or k > end:
			return
		low, high = start, end
		key = numbers[low]
		while low < high:
			while low < high and numbers[high] >= key:
				high -= 1
			numbers[low] = numbers[high]
			while low < high and numbers[low] <= key:
				low += 1
			numbers[high] = numbers[low]
		numbers[low] = key
		if low < k:
			self.PartitionOfK(numbers, start + 1, end, k)
		elif low > k:
			self.PartitionOfK(numbers, start, end - 1, k)
	def GetLeastNumbers_Solution(self, tinput, k):
		if k < 0 or tinput == [] or k > len(tinput):
			return []
		self.PartitionOfK(tinput, 0, len(tinput) - 1, k)
		return sorted(tinput[0:k])		

代码效果图如图所示:

  上题中由于只是输出一个数字,因此用什么算法都不影响其执行效率 ,但是在本题中,由于K的值是变化的,输出的值的个数随着K的变化而变化,这个复杂度就是呈线性的。因此,我们上面的解法很明显存在两个关键的问题。如果是对于K的值很小则还是没有影响,但如果K很大,则就会导致算法失败。由于上述的算法是将海量的数组存在内存里,这显然不合适的 。另外就是Partition把数组的顺序改变了 。从本题分析,我们其实可以不难看出就是首先进行数组排序,然后直接将取出前K个元素。但是关键就是我们用哪一种排序才能使我们的算法的时间复杂度降低呢。根据我们数据结构中各种排序算法的时间复杂度如下: 基于堆排序算法,构建最大堆。时间复杂度为O(nlogk); 如果用快速排序,时间复杂度为O(nlogn); 如果用冒泡排序,时间复杂度为O(n*k);   不难发现,其实我们用堆排序是最好的。接下来我们大致说一下堆排序算法。在维基百科中,堆排序是这样定义的:堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。若以升序排序说明,把数组转换成最大堆(Max-Heap Heap),这是一种满足最大堆性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。重复从最大堆取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆维持最大堆性质。   通常堆是通过一维数组来实现的。在数组起始位置为0的情形中: 父节点i的左子节点在位置(2i + 1) 父节点i的右子节点在位置(2i + 2) 子节点i的父节点在位置floor((i - 1)/2)   在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作: 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点 创建最大堆(Build Max Heap):将堆中的所有数据重新排序 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算 如下图就是一个大顶堆的构造过程:

  根据我们对堆排序的介绍,本题我们就是要构造大顶堆。判断从k~n-1位的数是否有可能是最小的k个数,只需与堆根进行比较,当它比跟堆大的时候,它肯定比堆内其他数都大,即它无希望排进前K个数。若有希望排进,那么需将目前堆内最大的数赶出,正好又是堆根,两者交换即可。这也就是我们为什么构建大顶堆而不是小顶堆的原因。我们分别用java和python将其实现: 1、用python实现:

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if tinput == [] or k <= 0 or k > len(tinput):
            return []
        result = []
        for num in tinput:
            if len(result) < k:
                result.append(num)
            else:
                if num < max(result):
                    result[result.index(max(result))] = num
        return sorted(result)

代码效果图如图所示:

2、用java将其实现:

import java.util.*;
public class Solution{
	public ArrayList<Integer>GetLeastNumbers_Solution(int [] input, int k){
		ArrayList<Integer> array = new ArrayList<Integer>();
		if(input == null || input.length == 0 || k <= 0 || k > input.length){
			return array;
		}
		for(int i = k/2; i >= 0; i--){
			buildMaxHeapSort(input, i, k);
		}
		for(int j = k; j < input.length; j++){
			if(input[j] < input[0]){
				swap(input, 0, j);
				buildMaxHeapSort(input, 0, k);
			}
		}
		for(int i = k - 1; i >= 0; i--){
			array.add(input[i]);
		}
			return array;
	}
	public void buildMaxHeapSort(int[] input, int i, int k){
		int leftChild = 2 * i;
		int rightChild = 2 * i + 1;
		int target = i;
		if(leftChild < k && input[i] < input[leftChild]){
			target = leftChild;
		}
		if(rightChild < k && input[target] < input[rightChild]){
			target = rightChild;
		}
		if(target != i){
			swap(input, i, target);
			buildMaxHeapSort(input, target, k);
		}
	}
	public void swap(int []input, int a, int b){
		int temp = input[a];
		input[a] = input[b];
		input[b] = temp;
	}
}

代码效果图如图所示:

总结

  本道题和上道题差不多还是考察数组的具体应用,我们首先借鉴上题思路四寻找第K个位置的数值方法找到第K个数字,然后在进行排序,虽然通过了代码测试,但是发现随着K的不断递增,其复杂度呈直线上升,因此,我们换了一种思路,在众多排序中,由于堆排序复杂度最低,就选择了堆排序,并且介绍了堆排序的相关知识点,根据本题的特点,用到了大顶堆的排序,并且分别用java和python将其实现。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] 堆排序 [2] weixin_30276935 [3] hustfc