一、TOPK问题
技巧,中等题就用优先队列,难题就用二分法
优先队列:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] result = new int[k];
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
for (Map.Entry<Integer, Integer> entry : entries) {
queue.offer(entry);
if (queue.size() > k) {
queue.poll();
}
}
for (int i = k - 1; i >= 0; i--) {
result[i] = queue.poll().getKey();
}
return result;
}
}
二分法:
上面面这题如果用优先队列的话,是可能会产生内存限制 因此提出了二分查找法:
二分查找算法步骤:
- 初始化
left = 1 , right = m*n , 进行二分搜索找到 k-th 小数字。 - 我们使用自定义的
getCount 函数来计算当前矩阵中小于等于mid 值的数字数量。 - 当二分搜索结束后,如果当前
count < k ,那么我们应该调整left值将其变大使得新的mid能逼近k, 及left = mid + 1 - 反之
count >= k ,那么我们应该调整right值将其变小使得新的mid也能逼近k, 及right = mid
getCount()方法
以此类推
代码:
class Solution {
public int getCount(int mid, int m, int n){
int i=m,j=1;
int count=0;
while(i>=1&&j<=n){
if(i*j<=mid){
count+=i;
j++;
}else{
i--;
}
}
return count;
}
public int findKthNumber(int m, int n, int k) {
int left = 1, right = m*n;
while(left<right){
int mid = (left+right)/2;
int count = getCount(mid,m,n);
if(count>=k){
right=mid;
}
if(count<k){
left = mid+1;
}
}
return left;
}
}
关于最后的mid一定会在数组中的问题:
我们先看getCount()函数 getCount()函数的目的是统计矩阵里小等于mid的元素数目count. 再判断count和k的关系.因为mid = (left + right) / 2这种划分方法是把矩阵划分成了[left , mid] 与[mid + 1, right]两部分. 当 count < k 时, 说明mid太小了, 我们应该在[mid + 1, right] 这个范围里查找. 否则在[left, mid]范围里查找.
如果存在一个不在矩阵中的数a满足条件, 因为a不在矩阵中,那count统计的元素肯定都是小于a的, 那一定存在一个比a小且在矩阵中的数b满足条件,即从小于a的数变成了小于等于b的数 .等用题目中的例子,x = 13 和x = 14 都满足小于等于x的元素数目等于8, 对14来说统计的都是小于它的数, 而对13来说统计的都是小于等于它的数. 问题来了, 那为何取到的不是14而是13呢?
因为我们取mid的取法是 mid = (left + right) / 2 , 当left < right 时, mid 永远 取不到right, 想要mid取到right ,只有left == right . 但循环条件是 while(left < right) , 当 left == right 时循环已经终止. 所以我们得到会是一个左边界. 还是用题目中的例子, 假设left = 13 , right = 14 则 mid = (13 + 14) / 2 = 13
|