面试中,拜托TopK,面试是别再问得比较多的几个问题之一,到底有几种方法,拜托这些方案里蕴含的面试优化思路究竟是怎么样的,今天和大家聊一聊。别再 画外音:除非校招,拜托我在面试过程中从不问TopK这个问题,面试默认大家都知道。别再 问题描述:从arr[1,拜托 n]这n个数中,找出***的面试k个数,这就是别再经典的TopK问题。 栗子:从arr[1,拜托 12]={ 5,3,7,1,8,2,9,4,7,2,6,6} 这n=12个数中,找出***的面试k=5个。 一、别再排序 排序是最容易想到的方法,将n个数排序之后,取出***的k个,即为所得。 伪代码: 时间复杂度:O(n*lg(n)) 分析:明明只需要TopK,却将全局都排序了,这也是云南idc服务商这个方法复杂度非常高的原因。那能不能不全局排序,而只局部排序呢?这就引出了第二个优化方法。 二、局部排序 不再全局排序,只对***的k个排序。 冒泡是一个很常见的排序方法,每冒一个泡,找出***值,冒k个泡,就得到TopK。 伪代码: 时间复杂度:O(n*k) 分析:冒泡,将全局排序优化为了局部排序,非TopK的元素是不需要排序的,节省了计算资源。不少朋友会想到,需求是TopK,是不是这***的k个元素也不需要排序呢?这就引出了第三个优化方法。 三、堆 思路:只找到TopK,不排序TopK。 先用前k个元素生成一个小顶堆,网站模板这个小顶堆用于存储,当前***的k个元素。 接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前***的k个元素。 直到,扫描完所有n-k个元素,最终堆中的k个元素,就是猥琐求的TopK。 伪代码: 时间复杂度:O(n*lg(k)) 画外音:n个元素扫一遍,假设运气很差,每次都入堆调整,调整时间复杂度为堆的高度,即lg(k),故整体时间复杂度是n*lg(k)。 分析:堆,云服务器提供商将冒泡的TopK排序优化为了TopK不排序,节省了计算资源。堆,是求TopK的经典算法,那还有没有更快的方案呢? 四、随机选择 随机选择算在是《算法导论》中一个经典的算法,其时间复杂度为O(n),是一个线性复杂度的方法。 这个方法并不是所有同学都知道,为了将算法讲透,先聊一些前序知识,一个所有程序员都应该烂熟于胸的经典算法:快速排序。 画外音: 其伪代码是: 其核心算法思想是,分治法。 分治法(Divide&Conquer),把一个大的问题,转化为若干个子问题(Divide),每个子问题“都”解决,大的问题便随之解决(Conquer)。这里的关键词是“都”。从伪代码里可以看到,快速排序递归时,先通过partition把数组分隔为两个部分,两个部分“都”要再次递归。 分治法有一个特例,叫减治法。 减治法(Reduce&Conquer),把一个大的问题,转化为若干个子问题(Reduce),这些子问题中“只”解决一个,大的问题便随之解决(Conquer)。这里的关键词是“只”。 二分查找binary_search,BS,是一个典型的运用减治法思想的算法,其伪代码是: 从伪代码可以看到,二分查找,一个大的问题,可以用一个mid元素,分成左半区,右半区两个子问题。而左右两个子问题,只需要解决其中一个,递归一次,就能够解决二分查找全局的问题。 通过分治法与减治法的描述,可以发现,分治法的复杂度一般来说是大于减治法的: 话题收回来,快速排序的核心是: 1. 这个partition是干嘛的呢? 顾名思义,partition会把整体分为两个部分。 更具体的,会用数组arr中的一个元素(默认是***个元素t=arr[low])为划分依据,将数据arr[low, high]划分成左右两个子数组: 以上述TopK的数组为例,先用***个元素t=arr[low]为划分依据,扫描一遍数组,把数组分成了两个半区: partition返回的是t最终的位置i。 很容易知道,partition的时间复杂度是O(n)。 画外音:把整个数组扫一遍,比t大的放左边,比t小的放右边,***t放在中间N[i]。 2. partition和TopK问题有什么关系呢? TopK是希望求出arr[1,n]中***的k个数,那如果找到了第k大的数,做一次partition,不就一次性找到***的k个数了么? 画外音:即partition后左半区的k个数。 问题变成了arr[1, n]中找到第k大的数。 再回过头来看看***次partition,划分之后: 画外音:这一段非常重要,多读几遍。 这就是随机选择算法randomized_select,RS,其伪代码如下: 这是一个典型的减治算法,递归内的两个分支,最终只会执行一个,它的时间复杂度是O(n)。 再次强调一下: 通过随机选择(randomized_select),找到arr[1, n]中第k大的数,再进行一次partition,就能得到TopK的结果。 五、总结 TopK,不难;其思路优化过程,不简单: 【本文为专栏作者“58沈剑”原创稿件,转载请联系原作者】 戳这里,看该作者更多好文