出现次数的Top K问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011386173/article/details/82427707

【题目】

给定String类型的数组strArr,再给定整数k,请严格按照排名顺序打印出现次数前k名的字符串。

【要求】

如果strArr长度为N,时间复杂度请达到O(Nlogk),

【测试用例】

  strArr = ["1", "2", "3", "4"],k = 2

  No.1:1, times:1

  No.2:2, times:1

 这种情况下,所有的字符串都出现一样多,随便打印任何两个字符串都可以。

  strArr = ["1", "1", "2", "3"],k = 2

  输出:

  No.1:1, times:2

  No.2:2, times:1

  或者输出:

  No.1:1, times:2

  No.2:3, times:1

【难度】
    ☆☆

【解答】

  首先遍历strArr并统计字符串的词频,例如,strArr = ["a","b","b","a","c"],遍历后可以生成每种字符串及其相关词频的哈希表如下:

key(字符串) value(相关词频)
“a” 2
“b” 2
“c” 1

用哈希表的每条信息可以生成Node类的实例,Node类如下:

public class Node {
    public String str;
    public int times;
    
    public Node(String s, int t) {
        str = s;
        t = times;
    }
}

哈希表中有多少信息,就建立多少Node类的实例,并且依次放入堆中,具体过程为:

1.建立一个大小为k的小根堆,这个堆放入的是Node类的实例;

2.遍历哈希表的每条记录,假设一条记录为(s,t),s表示一种字符串,s的词频为t,则生成Node类的实例,记为(str,times)。

  (1)如果小根堆没有满,则直接将(str,times)加入堆,然后进行建堆调整(heapInsert调整),堆中Node类实例之间都以词频(times)来进行比较,词频越小,位置越往上。

 (2)如果小根堆已满,说明此时小根堆已经选出k个最高词频的字符串,那么整个小根堆的堆顶自然代表已经选出的k个最高词频的字符串中,词频最低的那个。堆顶的元素记为(headStr,minTimes)。如果minTimes < times,说明字符串str有资格进入当前k个最高词频字符串的范围。而headStr应该被移出这个范围,所以把当前的堆顶(headStr, minTimes)替换为(str,times),然后从堆顶的位置进行堆的调整(heapify)。如果minTimes>=times,说明字符串str没有资格将进入当前k个最高词频字符串的范围,因为str的词频还不如目前选出的k个最高词频字符串中词频最少的那个,所以什么也不做。

3.遍历完strArr之后,小根堆里就是所有字符串中k个最高词频的字符串,但要求严格按排名打印,所以还需要根据词频从大到小完成k个元素间的排序。

遍历strArr简历哈希表的过程为O(N)。哈希表中记录的条数最多为N条,每条记录进堆时,堆的调整时间复杂度为O(logk),所以根据记录更新小根堆的过程为O(Nlogk)。k条记录排序的时间复杂度为O(klogk)。所以总的时间复杂度为O(N) + O(Nlogk) + O(klogk),即O(Nlogk)。具体过程见如下printTopKAndRank方法。

public 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 (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) {
				headp[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.print(heap[i].times);
		}
	}
}

public 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 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;
		}
		if (smallest != index) {
			swap(heap, smallest, index);
		} else {
			break;
		}
		index = smallest;
		left = index * 2 + 1;
		right  = index * 2 + 2;
	}
}

public void swap(Node[] heap, int index1, int index2) {
	Node tmp = heap[index1];
	heap[index1] = heap[index2];
	headp[index2] = tmp;
}

猜你喜欢

转载自blog.csdn.net/u011386173/article/details/82427707
今日推荐