前言:本文将介绍随机选择,分治法,减治法的思想,以及TopK问题优化的来龙去脉,原理与细节,保证有收获。

面试中,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个,即为所得。

伪代码

sort(arr, 1, n);

return arr[1, k];

时间复杂度:O(n*lg(n))

分析:明明只需要TopK,却将全局都排序了,这也是这个方法复杂度非常高的原因。那能不能不全局排序,而只局部排序呢?这就引出了第二个优化方法。

二、局部排序

不再全局排序,只对最大的k个排序。

冒泡是一个很常见的排序方法,每冒一个泡,找出最大值,冒k个泡,就得到TopK。

伪代码

for(i=1 to k){

bubble_find_max(arr,i);

}

return arr[1, k];

时间复杂度:O(n*k)

分析:冒泡,将全局排序优化为了局部排序,非TopK的元素是不需要排序的,节省了计算资源。不少朋友会想到,需求是TopK,是不是这最大的k个元素也不需要排序呢?这就引出了第三个优化方法。

三、堆

思路:只找到TopK,不排序TopK。

先用前k个元素生成一个小顶堆,这个小顶堆用于存储,当前最大的k个元素。

接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。

直到,扫描完所有n-k个元素,最终堆中的k个元素,就是猥琐求的TopK。

伪代码

heap[k] = make_heap(arr[1, k]);

for(i=k+1 to n){

adjust_heap(heep[k],arr[i]);

}

return heap[k];

时间复杂度:O(n*lg(k))

画外音:n个元素扫一遍,假设运气很差,每次都入堆调整,调整时间复杂度为堆的高度,即lg(k),故整体时间复杂度是n*lg(k)。

分析:堆,将冒泡的TopK排序优化为了TopK不排序,节省了计算资源。堆,是求TopK的经典算法,那还有没有更快的方案呢?

四、随机选择

随机选择算在是《算法导论》中一个经典的算法,其时间复杂度为O(n),是一个线性复杂度的方法。

这个方法并不是所有同学都知道,为了将算法讲透,先聊一些前序知识,一个所有程序员都应该烂熟于胸的经典算法:快速排序。

画外音:

(1)如果有朋友说,“不知道快速排序,也不妨碍我写业务代码呀”…额...

(2)除非校招,我在面试过程中从不问快速排序,默认所有工程师都知道;

其伪代码是

void quick_sort(int[]arr, int low, inthigh){

if(low== high) return;

int i = partition(arr, low, high);

quick_sort(arr, low, i-1);

quick_sort(arr, i+1, high);

}

其核心算法思想是,分治法。

分治法(Divide&Conquer),把一个大的问题,转化为若干个子问题(Divide),每个子问题“”解决,大的问题便随之解决(Conquer)。这里的关键词是“都”。从伪代码里可以看到,快速排序递归时,先通过partition把数组分隔为两个部分,两个部分“都”要再次递归。

分治法有一个特例,叫减治法。

减治法(Reduce&Conquer),把一个大的问题,转化为若干个子问题(Reduce),这些子问题中“”解决一个,大的问题便随之解决(Conquer)。这里的关键词是“只”

二分查找binary_search,BS,是一个典型的运用减治法思想的算法,其伪代码是:

int BS(int[]arr, int low, inthigh, int target){

if(low> high) return -1;

mid= (low+high)/2;

if(arr[mid]== target) return mid;

if(arr[mid]> target)

return BS(arr, low, mid-1, target);

else

return BS(arr, mid+1, high, target);

}

从伪代码可以看到,二分查找,一个大的问题,可以用一个mid元素,分成左半区,右半区两个子问题。而左右两个子问题,只需要解决其中一个,递归一次,就能够解决二分查找全局的问题。

通过分治法与减治法的描述,可以发现,分治法的复杂度一般来说是大于减治法的:

快速排序:O(n*lg(n))

二分查找:O(lg(n))

话题收回来,快速排序的核心是:

i = partition(arr, low, high);

这个partition是干嘛的呢?

顾名思义,partition会把整体分为两个部分。

更具体的,会用数组arr中的一个元素(默认是第一个元素t=arr[low])为划分依据,将数据arr[low, high]划分成左右两个子数组:

  • 左半部分,都比t大

  • 右半部分,都比t小

  • 中间位置i是划分元素

以上述TopK的数组为例,先用第一个元素t=arr[low]为划分依据,扫描一遍数组,把数组分成了两个半区:

  • 左半区比t大

  • 右半区比t小

  • 中间是t

partition返回的是t最终的位置i。

很容易知道,partition的时间复杂度是O(n)。

画外音:把整个数组扫一遍,比t大的放左边,比t小的放右边,最后t放在中间N[i]。

partition和TopK问题有什么关系呢?

TopK是希望求出arr[1,n]中最大的k个数,那如果找到了第k大的数,做一次partition,不就一次性找到最大的k个数了么?

画外音:即partition后左半区的k个数。

问题变成了arr[1, n]中找到第k大的数。

再回过头来看看第一次partition,划分之后:

i = partition(arr, 1, n);

  • 如果i大于k,则说明arr[i]左边的元素都大于k,于是只递归arr[1, i-1]里第k大的元素即可;

  • 如果i小于k,则说明说明第k大的元素在arr[i]的右边,于是只递归arr[i+1, n]里第k-i大的元素即可;

画外音:这一段非常重要,多读几遍。

这就是随机选择算法randomized_select,RS,其伪代码如下:

int RS(arr, low, high, k){

if(low== high) return arr[low];

i= partition(arr, low, high);

temp= i-low; //数组前半部分元素个数

if(temp>=k)

return RS(arr, low, i-1, k); //求前半部分第k大

else

return RS(arr, i+1, high, k-i); //求后半部分第k-i大

}

这是一个典型的减治算法,递归内的两个分支,最终只会执行一个,它的时间复杂度是O(n)。

再次强调一下:

  • 分治法,大问题分解为小问题,小问题都要递归各个分支,例如:快速排序

  • 减治法,大问题分解为小问题,小问题只要递归一个分支,例如:二分查找,随机选择

通过随机选择(randomized_select),找到arr[1, n]中第k大的数,再进行一次partition,就能得到TopK的结果。

五、总结

TopK,不难;其思路优化过程,不简单:

  • 全局排序,O(n*lg(n))

  • 局部排序,只排序TopK个数,O(n*k)

  • ,TopK个数也不排序了,O(n*lg(k))

  • 分治法,每个分支“都要”递归,例如:快速排序,O(n*lg(n))

  • 减治法,“只要”递归一个分支,例如:二分查找O(lg(n)),随机选择O(n)

  • TopK的另一个解法:随机选择+partition

知其然,知其所以然。

思路比结论重要。

希望大家对TopK有新的认识,谢转。

面试中常问的TOPK相关推荐

  1. java面试 设计模式_Java面试中常问到的设计模式

    面试中常问到的设计模式 什么是设计模式? ​设计模式是世界上各种各样程序员用来解决特定设计问题的尝试和测试的方法.设计模式是代码可用性的延伸 单例模式 ​保证被创建一次,节省系统开销 ​饿汉式:上来不 ...

  2. java io bio nio面试题_漫画:一文学会面试中常问的 IO 问题!

    原标题:漫画:一文学会面试中常问的 IO 问题! 作者 | 漫话编程 责编 | 伍杏玲 本文经授权转载自漫话编程(ID:mhcoding) 周末午后,在家里面进行电话面试,我问了面试者几个关于IO的问 ...

  3. 面试中常问的List去重问题,你都答对了吗?

    面试中经常被问到的list如何去重,用来考察你对list数据结构,以及相关方法的掌握,体现你的java基础学的是否牢固. 我们大家都知道,set集合的特点就是没有重复的元素.如果集合中的数据类型是基本 ...

  4. 去重 属性_面试中常问的List去重问题,你都答对了吗?

    面试中经常被问到的list如何去重,用来考察你对list数据结构,以及相关方法的掌握,体现你的java基础学的是否牢固. 我们大家都知道,set集合的特点就是没有重复的元素.如果集合中的数据类型是基本 ...

  5. 漫画:一文学会面试中常问的 IO 问题!

    作者 | 漫话编程 责编 | 伍杏玲 本文经授权转载自漫话编程(ID:mhcoding) 周末午后,在家里面进行电话面试,我问了面试者几个关于IO的问题,其中包括什么是BIO.NIO和AIO?三者有什 ...

  6. post大小限制_作为一个程序员,面试中常问的get和post的区别,你真的知道吗

    作为一个程序员无论是搞前端的还是搞后端的,只要问起来GET和POST的区别,都能说出来个一二三四来. 你可能自己写过无数个GET和POST请求,或者也看过很多权威网站总结出来的区别,你非常清楚的知道什 ...

  7. 面试中常问多线程相关的知识,在工作中到底用在哪里呢?

    求职面试中,面试官问到了高并发相关问题,该怎么办? 如果在求职面试的过程中,面试官问了你这个方向的问题,那你必须打起十二分的精神来回答这个问题,毕竟在各大厂招聘的JD中都提到了求职者具备[高并发经验] ...

  8. 面试中常问的你在项目开发中遇到过哪些问题怎么回答?

    一.你在项目开发中遇到过哪些问题? 此问题的回答一般从三个方向入手,一是技术问题,二是解决方案问题,三是甲方或团队配合问题,针对这三个方面的问题回答示例. 一.技术方面: 开发项目过程中遇到问题是难免 ...

  9. Android面试中常问的几种简单算法(两数之和、开灯、上楼梯、柠檬水、找最多的数)

    大家好,最近在面试,大小公司可能都会问一两个算法,来测试面试者的算法能力.本篇博客从笔者被问到的几个简单算法来给大家分享一下,(面试官会根据面试者的能力,提问不同难度,这篇的分享是比较简单的,大神可忽 ...

最新文章

  1. 参加软件测试培训需要学习哪些知识
  2. Linux更新pip国内源
  3. mysql 基于集_一种基于记录集查找特定行的方法_MySQL
  4. 笔记-信息化与系统集成技术-智慧城市建设参考模型
  5. SMSSMS垃圾邮件检测器的专业攻击
  6. Java 抽象类和接口的区别
  7. wordpress php 链接,WordPress中获取页面链接和标题的相关PHP函数用法解析
  8. NYOJ 81:炮兵阵地(状压DP)
  9. 排序算法专题-桶排序
  10. iOS模拟器录屏视频
  11. 计算机二级office学习之Excel操作题考点整理
  12. 易宝典——玩转O365中的EXO服务 之四十九 如何知道管理员进行了哪些操作
  13. 软件安全漏洞测试报告_现实生活中的软件安全漏洞以及如何确保安全
  14. C++/C语言-基本语法
  15. 分布式电商项目五:使用人人开源搭建前后分离的后台管理系统
  16. 软负载均衡和硬负载均衡
  17. 一文揭秘阿里、腾讯、百度的薪资职级
  18. 回归分析-(多元)线性回归分析基础( Linear Regression)
  19. DirectUI与QQ界面
  20. 数学笔记15——微积分第二基本定理

热门文章

  1. 给力回馈!亲~时间有限哦!
  2. python打开网页并输入数据_使用Python登陆网站读取表格数据
  3. 基于直方图的图像全局二值化算法原理、实现--基于谷底最小值的阈值
  4. 斐波那契数列 - 实现一个斐波那契数列,并且打印出来
  5. 文章本天成|跟我一起来一场简洁易懂的servlet的过滤器Filter的学习吧
  6. Hongment鸿蒙系统
  7. 我的2019“木桶插曲”——三个月考上研
  8. Compilation is not supported for following modules: example, image-viewer. Unfortunately you can't h
  9. Python JS逆向实战项目:某咕视频逆向分析 攻破~~
  10. 8-设计模式之行为型模式二(状态模式、观察者模式、中介者模式)