发现水贴(算法入门题目003)

题目

我们有一个ID列表,其中存着某论坛所有帖子的作者ID。
另外,我们已经得到准确情报,在这个长长的列表中,有3个ID,每个出现次数都超过了总数的1/4。这毫无疑问是职业水军干的。
你的任务是快速地找到这3个ID。
可以先考虑列表较短的时候,比如:
1,4,1,1,2,3,3,2,2,3
则所求ID为:1,2,3

分析

最容易想到的方案是:弄一个Map,记录每一个ID的出现次数。
最后再扫描一次,把次数大于总数的1/4的 ID 输出出来。
这个方案的问题在于:当列表较大,并且ID分布比较散的时候,我们这个Map可能很大,甚至是内存装不下。
有没有更节省的方案呢?

如果从实用性的角度讲,我们可以随机抽取若干个位置的ID,比如抽取10000个,从统计学理论(或直觉)可知,这个采样,应该保留了原列表的特征。所以对这个采样进行扫描处理就可以了。
但是不管怎么说,总有人说这个方法不严谨,并且有偷机取巧的意思。。。。

好吧,看看另一个思路。
我们注意到,3个ID发帖总数超过了3/4,也就是说,其他人发的帖子加起来也没有水军中一个人发的多!
于是乎,有人设计一个擂台赛:
擂台上可以同时容纳 3 个人,且开始是空的。
现在,队列中每个选手依次出场。
如果台上有自已人, 就把那个人的生命值加 1
如果台上有空位子,就站上台,且开始的生命数是 1
否则,就把台上 3 个人的生命值都减 1
生命值减到 0 的人死亡,把台上的位置空出来。

到最后,剩在台上的 ID,就是所求的 ID

代码

先来个笨办法的。
这在绝大多数场合是足够用的。

//problem003
import java.util.*;
public class A
{
	static List<Integer> f(int[] data){
		Map<Integer,Integer> map = new HashMap<Integer, Integer>();
		
		for(int x: data){
			Integer num = map.get(x);
			if(num==null) 
				map.put(x,1);
			else
				map.put(x, num+1);
		}
		
		List<Integer> res = new ArrayList<Integer>();
		
		for(int x: map.keySet()){
			if(map.get(x) > data.length / 4.0) res.add(x);
		}
		
		return res;
	}
	
	public static void main(String[] args){
		int[] data = {
			2,3,7,3,2,1,1,2,2,4,
			3,1,3,2,3,6,1,1,1,3,
			8,3,1,9,2,1,2,5,3,2,		
		};
		
		System.out.println(f(data));
	}
}

下面是高效的算法。

//problem003
import java.util.*;
public class B
{
	static Set<Integer> f(int[] data){
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		
		for(int x: data){
			if(map.get(x) != null){
				map.put(x, map.get(x)+1);
				continue;
			}
			if(map.size()<3){
				map.put(x, 1);
				continue;
			}
			for(Iterator<Integer> it=map.keySet().iterator(); it.hasNext();){
				int key = it.next();
				map.put(key, map.get(key)-1);
				if(map.get(key)==0) it.remove();				
			}	
		}
		
		return map.keySet();
	}
	
	public static void main(String[] args){
		int[] data = {
			2,3,7,3,2,1,1,2,2,4,
			3,1,3,2,3,6,1,1,1,3,
			8,3,1,9,2,1,2,5,3,2,		
		};
		
		System.out.println(f(data));
	}
}

这里需要初学java童鞋注意的是:
不能直接在Map的迭代中进行删除键的动作。
必须使用 Iterator,并且只对当前 Iterator 才能remove

详解

如果对为什么这样设计可行还是心里没底,可以在“千聊”上搜这个标题。
或者,手机扫下图,添加关注。
(最好是自已先仔细想想,挺有趣的)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值