以LeetCode的题目为例:
有 n 个人,每个人都有一个 0 到 n-1 的唯一 id 。
给你数组 w a t c h e d V i d e o s watchedVideos watchedVideos 和 f r i e n d s friends friends ,其中 w a t c h e d V i d e o s [ i ] watchedVideos[i] watchedVideos[i] 和 f r i e n d s [ i ] friends[i] friends[i] 分别表示 id = i 的人观看过的视频列表和他的好友列表。
Level 1 的视频包含所有你好友观看过的视频,level 2 的视频包含所有你好友的好友观看过的视频,以此类推。一般的,Level 为 k 的视频包含所有从你出发,最短距离为 k 的好友观看过的视频。
给定你的 id 和一个 level 值,请你找出所有指定 level 的视频,并将它们按观看频率升序返回。如果有频率相同的视频,请将它们按名字字典序从小到大排列。
示例1:
输入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 1
输出:["B","C"]
解释:
你的 id 为 0 ,你的朋友包括:
id 为 1 -> watchedVideos = ["C"]
id 为 2 -> watchedVideos = ["B","C"]
你朋友观看过视频的频率为:
B -> 1
C -> 2
示例2:
输入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 2
输出:["D"]
解释:
你的 id 为 0 ,你朋友的朋友只有一个人,他的 id 为 3 。
分析:
要想得到最终的视频列表,首先要获得指定level的朋友的名单,显然题目中是一个广度优先搜索,且已经遍历过的节点(朋友)不需要再遍历,由此可通过一个队列来实现得到相应level的friends名单:
int num = watchedVideos.size();
LinkedList<Integer> friend_l = new LinkedList();
boolean[] isSearched = new boolean[num];
friend_l.offer(id);
isSearched[id] = true;
int l = 0;
while(!friend_l.isEmpty() && l < level) {
LinkedList<Integer> temp = new LinkedList();
while(!friend_l.isEmpty()) {
int cur = friend_l.pollFirst();
for(int i : friends[cur]) {
if(!isSearched[i]) {
temp.offer(i);
isSearched[i] = true;
}
}
}
friend_l = temp;
l++;
}
得到了第level层friends的名单后,我们希望得到所有video的列表,由于每种video可能出现多次,很自然的想法是用一个Map来记录video和出现的次数。
HashMap<String, Integer> map = new HashMap();
for(int i : friend_l) {
for(String v : watchedVideos.get(i)) {
if(map.containsKey(v)) {
map.put(v, map.get(v) + 1);
} else {
map.put(v, 1);
}
}
}
由此我们得到了video的无序Map。接下来我们按频率,也就是map的value来对其进行排序,就得到了最后的结果。
对Map进行排序
方法1:重写compare方法
List<Map.Entry<String, Integer>> res = new ArrayList();
for(Map.Entry<String, Integer> entry : map.entrySet()) {
res.add(entry);
}
Collections.sort(res, new Comparator<Map.Entry<String, Integer>>(){
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
if(e2.getValue() != e1.getValue()) {
return e1.getValue() - e2.getValue();
} else {
return e1.getKey().compareTo(e2.getKey());
}
}
});
方法2:构造优先队列
Queue<Map.Entry<String, Integer>> queue = new PriorityQueue(new Comparator<Map.Entry<String, Integer>>(){
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
if(e2.getValue() != e1.getValue()) {
return e1.getValue() - e2.getValue();
} else {
return e1.getKey().compareTo(e2.getKey());
}
}
});
for(Map.Entry<String, Integer> entry : map.entrySet()) {
queue.add(entry);
}
List<String> ret = new ArrayList();
while(!queue.isEmpty())
ret.add(queue.poll().getKey());
方法3:自己造轮子,写一个快排、堆排序或者归并排序
private void quickSort(Map.Entry<String, Integer>[] entrys, int start, int end) {
if(start >= end)
return;
int l = start;
int r = end;
Map.Entry<String, Integer> cur = entrys[start];
boolean flag = true;
while(l < r) {
if(flag) {
if(entrys[r].getValue() > cur.getValue()) {
r--;
} else if(entrys[r].getValue() < cur.getValue()){
entrys[l] = entrys[r];
l++;
flag = !flag;
} else {
if(entrys[r].getKey().compareTo(cur.getKey()) > 0) {
r--;
} else {
entrys[l] = entrys[r];
l++;
flag = !flag;
}
}
} else {
if(entrys[l].getValue() < cur.getValue()) {
l++;
} else if(entrys[l].getValue() > cur.getValue()){
entrys[r] = entrys[l];
r--;
flag = !flag;
} else {
if(entrys[l].getKey().compareTo(cur.getKey()) < 0) {
l++;
} else {
entrys[r] = entrys[l];
r--;
flag = !flag;
}
}
}
}
entrys[l] = cur;
quickSort(entrys, start, l - 1);
quickSort(entrys, l + 1, end);
}
之后调用自己写的快速排序:
Map.Entry[] entrys = new Map.Entry[map.size()];
int idx = 0;
for(Map.Entry<String, Integer> entry : map.entrySet()) {
entrys[idx] = entry;
idx++;
}
quickSort(entrys, 0, map.size() - 1);
List<String> ret = new ArrayList();
for(Map.Entry<String, Integer> entry : entrys)
ret.add(entry.getKey());
return ret;
最后的效果是:重写Compare与自己造轮子的速度几乎一致,为37ms,这是因为默认的排序方法就是快速排序。使用优先队列稍微快一些,用时31ms。