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
- 使用 Roslyn 编译器服务
- ExtJS4预览:渲染过程重构和标准化
- Ext JS 4预览:更快、更简单、更稳定
- VR技术的进步推动工业机器人革命
- tLinux 2.2下安装Mono 4.8
- WordPress评论回复邮件样式美化教程
- 基于Aspose.Pdf把pdf文件每一页转换为图片
- 分布式文件存储的数据库开源项目MongoDB
- 使用 JavaScriptService 在.NET Core 里实现DES加密算法
- 拒绝全英文垃圾评论!仅用代码实现
- 在.NET Core 里使用 BouncyCastle 的DES加密算法
- 微信小程序:为了满足三方需求,我们一直在改变
- 让陌生人能够相互自由交易和支付
- 斯坦福发布首份 AI Index 报告,AI 研究者不再茫然
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 简单好用的Adapter—ArrayAdapter详解
- Android开发之图片切割工具类定义与用法示例
- Android开发之超强图片工具类BitmapUtil完整实例
- Android使用URL读取网络资源的方法
- Android控件RecyclerView实现混排效果仿网易云音乐
- MUI整合上拉下拉的写法
- 详解OkSocket与Android的简单使用
- 浅谈Android Studio 3.0 的一些小变化
- 详解如何使用Android Studio开发Gradle插件
- Android ItemDecoration 实现分组索引列表的示例代码
- Android TextView实现词组高亮的示例代码
- Android开发使用json实现服务器与客户端数据的交互功能示例
- Android中实现词组高亮TextView方法示例
- AsyncTask类实例详解
- Android中ListView的item点击没有反应的解决方法