一道关于随机算法的面试题(转)

简介: 今天碰到了一道面试题:原题大致是,每首歌曲都是一个评分,现在有2000首歌曲,要求实现一个随机播放器,每首歌曲播放的概率应该正比于它的评分,例如评分9.1的歌曲,和评分7.9的歌曲,播放的次数应该是91:79。

今天碰到了一道面试题:原题大致是,每首歌曲都是一个评分,现在有2000首歌曲,要求实现一个随机播放器,每首歌曲播放的概率应该正比于它的评分,例如评分9.1的歌曲,和评分7.9的歌曲,播放的次数应该是91:79。

面试官给的答案是大致如此:

先把评分从小到大排序,之后把根据每首歌的评分,生成一个半闭开区间,然后生成一个随机数,看随机数落在哪个区间,就是选择的那首歌。例如,有三首歌,评分是[1,2,3] 那么应该是生成三个区间 [0-1,1-3,3-6],之后生成一个0-6之间的随机数,随机数落在哪个区间,就选择对应的歌曲。考虑排序的效率,这是一个nLogn的算法。

但是,这个算法是有纰漏的,没有考虑到评分重复的情况,如果三首歌的评分是[1,2,2],那么应该是生成两个区间[0-1,1-5], 如果落在第二个区间,还需要从两首评分为2的歌曲里面随机选出一首来。这样的话,实现起来就相当复杂了。

最后,如果照上面那样考虑,就完整了,但是实现起来的话,会发现,没有很好的数据结构能判断哪个随机数是落在哪个区间的,除非遍历所有的区间。

那么,优雅又高效的解法是什么样的,假定每个评分只到小数点后一位,那么其实,利用空间换取时间的思路,只需要把每首歌按照他的评分多少相应的复制多少重复的歌曲,并且把所有的歌曲都扔到一个池子里面,之后从池子里面等概率的选取一首歌就行了。在最坏的情况下,2000首歌曲的评分都是9.9,那么每首歌需要复制99首,空间效率是On,时间复杂度为O1

算法的scala实现如下:

复制代码
class RandomSong(val rate: Array[Double]) {
  val rateWithIndex = rate.map(x => (x * 10).toInt).zipWithIndex
  val songPool = rateWithIndex.flatMap { case (rate, index) => Array(index).padTo(rate, index)}

  def pickSong:Int = songPool(Random.nextInt(songPool.size))

}
复制代码

测试

复制代码
object main {
  def main(args: Array[String]) {
    val r = new RandomSong(Array(0.9,0.9,0.1,0.2))
    var count: Map[Int, Int] = Map()
    1 to 10000 foreach { x =>
      val song = r.pickSong
      count.get(song) match {
        case None => count += (song -> 1)
        case Some(n) => count += (song -> (1 + n))
      }
    }
    println("count = " + count)
  }
}
复制代码

结果

count = Map(2 -> 477, 1 -> 4312, 3 -> 970, 0 -> 4241)

ps:我是回家路上才想起这种解法的,我和我老婆说,化学系毕业的她直接就给出了正确的解法,哎,被数学学霸碾压的滋味就是这么销魂。

 

更新:早上和V站的V友讨论以后,发现面试官说的那种映射是可以实现的,例如有三首歌,评分是[1,2,3]那么区间段是[0-1,2-4,4-6]这个时候,只需要存一个数组[1,4,6],之后用2分查找就能得出正确的结论了,当然还需要考虑评分重复的情况。

rangeMap guava中有现成的实现,我还是太年轻啊。此外,这种加权随机的算法,早有研究

http://www.electricmonk.nl/Writings/HomePage?action=download&upname=weighted_random_dist.pdf

http://www.electricmonk.nl/log/2009/12/23/weighted-random-distribution/

http://www.cnblogs.com/javanerd/p/4504482.html

 

相关文章
|
1月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
1月前
|
机器学习/深度学习 算法 数据中心
【机器学习】面试问答:PCA算法介绍?PCA算法过程?PCA为什么要中心化处理?PCA为什么要做正交变化?PCA与线性判别分析LDA降维的区别?
本文介绍了主成分分析(PCA)算法,包括PCA的基本概念、算法过程、中心化处理的必要性、正交变换的目的,以及PCA与线性判别分析(LDA)在降维上的区别。
43 4
|
1月前
|
机器学习/深度学习 算法
【机器学习】解释对偶的概念及SVM中的对偶算法?(面试回答)
解释了对偶的概念,指出对偶性在优化问题中的重要性,尤其是在强对偶性成立时可以提供主问题的最优下界,并且详细阐述了支持向量机(SVM)中对偶算法的应用,包括如何将原始的最大间隔优化问题转换为对偶问题来求解。
45 2
|
1月前
|
机器学习/深度学习 算法 数据挖掘
|
28天前
|
消息中间件 存储 算法
这些年背过的面试题——实战算法篇
本文是技术人面试系列实战算法篇,面试中关于实战算法都需要了解哪些内容?一文带你详细了解,欢迎收藏!
|
2月前
|
算法 Java
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
40 1
|
3月前
|
消息中间件 算法 Java
抖音面试:说说延迟任务的调度算法?
Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
42 5
抖音面试:说说延迟任务的调度算法?
|
2月前
|
算法 Java 程序员
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
31 0
|
2月前
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
26 0
|
2月前
|
存储 算法 Java
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
28 0