1. 程式人生 > >面試題:出現次數的Top K問題

面試題:出現次數的Top K問題

題目:
給定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