锁定老帖子 主题:迅雷的一个面试题
精华帖 (8) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (1)
|
|
---|---|
作者 | 正文 |
发表时间:2012-03-01
select row_num from (select *,rownum as row_num from tableName) where id=#id#
|
|
返回顶楼 | |
发表时间:2012-03-01
phk070832 写道 case0079 写道 为什么不能用数据库解决呢?
又不是说只有一个数据库。 数据库能比内存快吗 两亿条数据显然是放在磁盘上的。数据库最终也是以文件形式放在磁盘上的。所以根本没有必要争论是什么数据源,放在数据库里根本没什么不妥。 sql语句最终也是一段代码,一个算法的实现。在数据库中score会按btree排序的。如果数据库的count算法实现的好的话就可以用sql来计算排名。 |
|
返回顶楼 | |
发表时间:2012-03-01
HADOOP
|
|
返回顶楼 | |
发表时间:2012-03-01
DDT_123456 写道 HADOOP
如果数据没有排序的话可以考虑用HADOOP |
|
返回顶楼 | |
发表时间:2012-03-02
看了不少高人的回复,有点自己的想法,之前先有几个问题要先确认一下,1、数据源的是否可以直接获取某行记录?2、id是否指的用户的ID 还是只在这个表中做索引用的标识?(这个问题好像也没什么意义,呵呵)
如果可以直接获取指定行的记录,那就用二分法直接查就好了:既然已知数据有2E条,那取第1E行记录比较……下略; 如果不可以真接获取到指定行记录,那单条记录的宽度是否一样(好像这也问的有点多余),如果一样,那设 w=单记录宽度,row=1E * w,取row到row+w之间的数据……下面和前面提到的一样。 其实就是用二分法比较,这个和内存没多大关系。想提高效率的话,前面有高人也提过了,就是建立一个分数区段的索引,根据内存的大小确定区段的长度就好了。 如果数据源只是一个流的话,那就慢慢比吧,没好办法。 |
|
返回顶楼 | |
发表时间:2012-03-02
最后修改:2012-03-02
正解!内存中常数时间(微秒级)解决问题。
hardPass 写道 tsxm 写道 hardPass 写道 就一定要用数据表来完成么?
CPU,内存,外存 同学们啊! 桶排序呗, 直接在程序中搞个数组,数组的下标就是分数,元素是每个分数对应的人数。 具体的,如果不需要精确计算,为了减少同步及遍历计算的性能损失,可以为每个数组元素设定一个预留值。 都说内存不够了,用外存估计速度太慢 1G内存还不够?如果现在排名第一的是1亿分,也就是一个长度为1亿的数组而已 300MB左右就够了,剩下的内存还可以再维护个计数缓存的数组(每个元素是上面数组希格玛,0到i),数组长度是上面数组一样 |
|
返回顶楼 | |
发表时间:2012-03-02
因为是已经排序过的所以可以用分端映射 + 2分法.
将2E条数据按每段1000w条分成20组,每组只记录Min Score和Max Score. 这步可以是比较占用资源的,可以后台定时Build. 然后查询的时候用2分法查询Segment,如果命中在哪个Segment里就在Segment里查询. 将Segment分的越小查询速度越快,但占用资源就越多. |
|
返回顶楼 | |
发表时间:2012-03-02
jiuyuehe 写道 shoushou2001 写道 假设:
分区:1 2 3 4 5。。。 分数:[0, 1000),[1000, 2000),[2000, 3000),[3000, 4000),[)。。。 分别给id、score加索引,用户一登录,就知道id,通过id查找score,即可知道该score所在的分区,如score=2300,位于分区3. 通过select计算3以后的比2300大的共有多少个,可以通过分数范围,一个分区一个分区地求,最后累加的结果再加1就是该用户的排名。 --------------- 允许扔XX,但请手下留情 id 都被分开了,怎么通过找id? 成千上万的用户积分在不断变化 定时更新这些分区表是不是很麻烦呢? |
|
返回顶楼 | |
发表时间:2012-03-07
CshBBrain 写道 kidding87 写道 终于有人说了我说的东西。。。
你们难道就没发现别人说的是数据源,不是数据库 那么多数据select * from table where id=?没有索引这个的效率都低死,数据库还不是一个个找 的确,题目强调了是【数据源】,并且是有序的数据源,没有说具体的存储形式,用数据库,用文件系统,还是用缓存服务器集群......;其实大多数人一看到2亿条数据都想到自己采用怎样形式去存这个数据了,而忽略题目的本意是怎样快速有效的找出用户的排名;依我个人的愚见,用户登录系统时找到用户的分数排名,要到数据库去找,或是到文件系统去找花费不菲而且效率也不见得好,一般会放到缓存服务器集群中。当然其实数据源的存储形式我们不用去操心。用户登录肯定已经获取到id和自己的分数,我们就假设已经知道用户id和用户分数(这是比较合理的假设)。 数据源既然【有序】那么我们肯定就有办法(是什么办法根据数据源具体的存储形式可以写个方法<当然有可能数据源本身可以提供>)取到指定位置的元素。 1.获取数据源的最后一个元素和数据源的第一个元素的 score, 将这2个score 相减 得到的 最大分数与最小分数的差值 S1。 2.用得到的分数差值 除以 数据源的元素数量 得到 平均每个用户之间的 分数差 V1; 3.将用户的分数US 减去 数据源中最小分数 得到用户与最小 分数之间的差值 S2, 4.W = S2 / V1 ,W 为我们估算的用户在所有用户中的排名数量 5.取出W处的元素的score WS 6.如果 用户的分数US 小于 WS,则从 W处开始向前查找用户的排名,如果US大于 WS 则从W处开始向后查找用户的排名,用户的排名就在附近,不用加载太多数据。如果US的只等于WS,那么恭喜你了。 7.这个算法没有考虑分数相同并列排名的处理,我想迅雷也许也没有处理并列排名的情况,只是获取数据源中的具体位置。 8.如果分数分布特别不均匀的话,可以递归使用上面的方法多算几次,直到US 和 WS 相差非常小(小到一个阀值)再从W处开始向前或向后查找定位。 同意这个方案 |
|
返回顶楼 | |
发表时间:2012-03-16
最后修改:2012-03-16
废话不说了,上代码,有兴趣朋友跑一下.
产生模拟文件(1,600,000,000B) + 实际算法。 import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Random; //@author piaoboyi@gmail.com public class BinarySearch { public static int nCount = (int) 2e8; //!!!!!!!!!!!!!!!!!! Choose disk that is large enough !!!!!!!!!!!!!! public static String path = new String("D:/data.dat"); public static String testPath = new String("D:/test.dat"); public static Random r = new Random(); public static void BuildFile() throws IOException { // Be sure to use buffer. DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(new File(path)))); int nCurrentScore = 0; for (int i = 0; i < nCount; i++) { // int nID = r.nextInt(100); // nID is not so useful. int nID = 12341231; out.writeInt(nID); // Becareful to generate the score. because score is small that sizeof(int)/2. // Which makes random number should < 20. nCurrentScore += r.nextInt(5); out.writeInt(nCurrentScore); } out.close(); } public static void BuildTestFile() throws IOException { DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(new File(testPath)))); // 25|1 // 31|2 // 17|3 // ... out.writeInt(25); out.writeInt(1); out.writeInt(31); out.writeInt(2); out.writeInt(17); out.writeInt(3); out.close(); } public static long BSearch(String path, int nScore) throws IOException { RandomAccessFile raf = new RandomAccessFile(path, "r"); assert (raf.length() % 8 == 0); long l = 0, r = raf.length() / 8 - 1; while (l <= r) { long m = (l + r) / 2; long pointer = m * 8; raf.seek(pointer); @SuppressWarnings("unused") int id = raf.readInt();// id. int score = raf.readInt(); if (nScore == score) { raf.close(); return m; } if (nScore < score) r = m - 1; else l = m + 1; } raf.close(); return -1L; } public static void main(String[] args) throws IOException { // BuildTestFile(); long a = System.currentTimeMillis(); BuildFile(); long b = System.currentTimeMillis(); System.out.println((b-a)/(1000)); System.out.println("Rank is " + BSearch(path, 0)); } } 数据文件产生时间大概100s,大家耐心点哈! 典型磁盘访问时间是10ms,最多31次IO也就310ms.经测试,哥磁盘比典型磁盘快点. |
|
返回顶楼 | |