题目:
给定String类型的数组strArr,再给定整数k,请严格按照排名顺序打印出现次数前k名的字符串。
举例:
strArr=[“1”,”2”,”3”,”4”], k=2
No.1:1,times:1
No.1:2,times:1
这种情况下,所有的字符串都出现一样多,随便打印任何两个字符串都可以。
strArr=[“1”,”1”,”2”,”3”], k=2
No.1:1,times:2
No.1:2,times:1
或则
No.1:1,times:2
No.1:3,times:1
要求:如果strArr长度为N,时间复杂度请达到O(Nlogk)
解答:
首先遍历strArr并统计字符串的词频,例如,strArr=[“a”,”b”,”b”,”a”,”c”],遍历后可以生成每种字符串及其相关词频的哈希表:
用哈希表的每条信息可以生成Node类的实例,Node类如下:
public class Node{
public String str;
public int times;
public Node(String s,int t){
str=s;
times=t;
}
}
哈希表中有多少信息,就建立多少Node类的实例,并且依次放入堆中,具体过程为:
(1)建立一个大小为k的小根堆,这个堆放入的是Node类的实例。
(2)遍历哈希表的每条记录,假设一条记录为(s,t),s表示一种字符串,s的词频为t,则生成Node类的实例,记为(str,times)。
a、如果小根堆没有满,就直接将(str,times)加入堆,然后进行建堆调整(heapInsert调整),堆中Node类实例之间都以词频(times)来进行比较,词频越小,位置越往上。
b、如果小根堆已满,说明此时小根堆已经选出k个最高词频的字符串,那么整个小根堆的堆顶自然代表已经选出的k个最高词频的字符串中,词频最低的那个。堆顶的元素记为(headStr,minTimes)。如果minTimes小于times,说明字符串str有资格进入当前k个最高词频字符串的范围。而headStr应该被移出这个范围,所以把当前的堆顶(headStr,minTimes)替换成(str,times),然后从堆顶的位置进行堆的调整(heapify),如果minTimes>=times,说明字符串str没有资格进入当前k个最高词频字符串的范围,因为str的词频还不如目前选出的k个最高词频字符串中词频最少的那个,所以说明也不做。
c、遍历完strArr之后,小根堆里就是所有字符串中k个最高词频的字符串,但要求严格按排名打印,所以还需要根据词频从大到小完成k个元素间的排序。
遍历strArr建立哈希表的过程是O(N),哈希表中记录的条数最多为N条,每一条记录进堆时,堆的调整时间复杂度为O(logk),所以根据记录更新小根堆的过程为O(Nlogk)。k条记录排序的时间复杂度为O(klogk),所以总的时间复杂度为O(N)+O(Nlogk)+O(klogk),即O(Nlogk),具体代码如下:
package QuestionTest;
import java.util.HashMap;
import java.util.Map;
/**
* Created by L_kanglin on 2017/4/23.
* 出现次数的top K问题
*/
public class Test20 {
public static class Node{
public String str;
public int times;
public Node(String s,int t){
str=s;
times=t;
}
}
public static void main(String[] args){
String[] strArr={"a","b","b","a","c"};
int k=2;
printTopKAndRank(strArr,k);
}
public static void printTopKAndRank(String[] arr,int topK){
if(arr==null|| topK<1){
return;
}
HashMap<String,Integer> map=new HashMap<String,Integer>();
//生成哈希表(字符串词频)
//注意词频表的处理
for(int i=0;i!=arr.length;i++){
String cur = arr[i];
if(!map.containsKey(cur)){
map.put(cur,1);
}else{
map.put(cur,map.get(cur)+1);
}
}
Node[] heap =new Node[topK];
int index=0;
for(Map.Entry<String,Integer> entry:map.entrySet()){
String str=entry.getKey();
int times=entry.getValue();
Node node=new Node(str,times);
if(index!=topK){
heap[index]=node;
heapInsert(heap,index++);
}else{
if(heap[0].times<node.times){
heap[0]=node;
heapify(heap,0,topK);
}
}
}
//把小根堆的所有元素按词频从大到小排序
for(int i=index-1;i!=0;i--){
swap(heap,0,i);
heapify(heap,0,i);
}
//严格按照排名打印k条记录
for(int i=0;i!=heap.length;i++){
if(heap[i]==null){
break;
}else{
System.out.print("No."+(i+1)+": ");
System.out.print(heap[i].str+",times: ");
System.out.println(heap[i].times);
}
}
}
public static void heapInsert(Node[] heap,int index){
while(index!=0){
int parent =(index-1)/2;
if(heap[index].times<heap[parent].times){
swap(heap,parent,index);
index=parent;
}else{
break;
}
}
}
public static void heapify(Node[] heap,int index,int heapSize){
int left=index*2+1;
int right=index*2+2;
int smallest=index;
while(left<heapSize){
if(heap[left].times<heap[index].times){
smallest=left;
}
if(right<heapSize && heap[right].times<heap[smallest].times){
smallest=right;
}else{
break;
}
index=smallest;
left=index*2+1;
right=index*2+1;
}
}
public static void swap(Node[] heap,int index1,int index2){
Node tmp=heap[index1];
heap[index1]=heap[index2];
heap[index2]=tmp;
}
}
运行结果如下:
No.1: b,times: 2
No.2: a,times: 2