[排序 二分查找] 274. H 指数(比较排序 → 计数排序)275. H 指数 II(线性查找 → 二分查找)
274. H 指数(citations数组无序)
题目链接:https://leetcode-cn.com/problems/h-index/
分类:
- 数学:新概念“h指数” ,即寻找最小的 i 使len-i <= citations[i]成立,len - i 就是h指数;
- 排序:
- 比较排序:Arrays.sort对数组排序,在有序数组中寻找第一个满足len-i <= citations[i]的 i;
- 计数排序:辅助数组count统计citations中“不同被引用次数”对应的论文数量(即count[i]存放被引用次数 = i 的论文个数),从后往前寻找第一个满足 i <= sum(count[n … i])的 i 。
.
.
思路1:排序 (O(NlogN))
首先,将数组排序,例如citations = [3,0,6,1,5],排序后得到:[0,1,3,5,6];
然后,遍历排序数组寻找最大的h指数,即寻找满足len - i <= citations[i] 的最大len - i,也就是寻找最小的 i 使len - i <= nums[i]成立,此时len - i就是最大的h指数。
class Solution {
public int hIndex(int[] citations) {
Arrays.sort(citations);
int len = citations.length;
//寻找排序数组里满足len-i>=nums[i]的最大i
int res = 0;
for(int i = 0; i < len; i++){
if(citations[i] >= len - i){
return len - i;
}
}
return res;
}
}
- 时间复杂度:O(NlogN)
思路2:计数排序(O(N))
开辟一个数组count,count[i]存放的是被引用次数 == i的论文个数。但citations数组中元素值大小没有固定的上界,也就是论文引用次数没有固定上界,那么count数组的大小就不好设置了。
这里我们可以利用一个性质:h指数最大不可能超过 citations数组的大小,
例如:[100,100,100,100,100],h=5,
所以我们在对citations做计数排序时,如果遇到元素值 > citations.length的元素,就取作citations.length,而限制最大引用次数并不会影响到h的结果。所以count数组的大小设置为[len+1]即可。
接下来对citations数组做计数排序,统计各个引用次数下的论文个数,填充到count数组:
例如:citations=[1,3,2,3,100]
citations[0]=1,则count[1]++;
citations[1]=3,则count[3]++;
citations[2]=2,则count[2]++;
citations[3]=3,则count[3]++;
citations[4]=5(100>5,取5),则count[5]++;
最终得到:count=[0,1,1,2,0,1]
接着在count数组上统计被引用至少 i 次的论文个数,用sumCount数组来存放:
- 被引用至少 i 次的论文个数 = count[i]+count[i+1]+…+count[n]。
例如:count=[0,1,1,2,0,1]
i=0表示论文至少被引用0次,则count[i=0~n]之和=5,即有5篇论文至少被引用0次,所以sumCount[0]=5;
i=1表示论文至少被引用1次,则count[i=1~n]之和=5,即有5篇论文至少被引用1次,所以sumCount[1]=5;
i=2表示论文至少被引用2次,则count[i=2~n]之和=4,即有4篇论文至少被引用2次,所以sumCount[2]=4;
...以此类推,可以得到:
sumCount=[5,5,4,3,1,1],sumCount[i]表示至少有 i 次引用的论文数量。
最后,我们在sumCount中寻找最大的满足 i <= sumCount[i] 的 i 就是h指数,所以这里的h指数=3.
实现代码:
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int[] count = new int[n + 1];//count[i]存放的是被引用次数==i的论文个数
//citations做计数排序
for(int elem : citations){
count[Math.min(n, elem)]++;
}
int[] sumCount = new int[n + 1];//sumCount[i]表示至少有 i 次引用的论文数量
sumCount[n] = count[n];
for(int i = n - 1; i >= 0; i--){
sumCount[i] = sumCount[i + 1] + count[i];
}
//寻找sumCount中的h指数
for(int i = n; i >= 0; i--){
if(i <= sumCount[i]) return i;
}
return 0;
}
}
- 时间复杂度:O(N),计数排序需要遍历一次数组O(N),构造sumCount需要O(N),寻找h指数需要O(N),实际时间复杂度为O(3N)。
- 空间复杂度:O(N),使用到了count,sumCount两个辅助数组,实际空间复杂度为O(2N).
优化:
-
空间优化:第二个辅助数组sumCount可以省略,我们可以对count数组从后往前遍历,设置一个sum变量在遍历过程迭更新来替代 i <= sumCount[i] 中的sumCount[i]。(和常用的动态规划空间优化手段类似)
-
时间优化:在sum变量迭代更新的同时可以寻找最大的满足i <= sum的i,也就是从后往前找到的第一个 <= sum的i,可以减少一次遍历数组的过程。
//思路2的优化版本
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int[] count = new int[n + 1];//count[i]存放的是被引用次数==i的论文个数
//citations做计数排序
for(int elem : citations){
count[Math.min(n, elem)]++;
}
int sum = count[n];//sumCount优化为一个迭代变量
//从后往前寻找h指数
for(int i = n; i >= 0; i--){
if(i <= sum) return i;
sum += count[i - 1];
}
return 0;
}
}
275. H 指数 II (citations数组有序)
题目链接:https://leetcode-cn.com/problems/h-index-ii/
分类:
- 数学:新概念“h指数” ,即寻找最小的 i 使len-i <= citations[i]成立,len - i 就是h指数;
- 查找:
- 线性查找:同274思路1,遍历数组寻找找第一个满足len-i <= citations[i]的 i (O(N));
- 二分查找:二分查找满足citations[i] >= len - i的最小 i (O(logN))。
题目分析
275和274相比,给定的citations数组是有序的,所以一个思路就是直接利用274的思路1实现O(N)用时的解法(思路1);
本题的考点在于,将查找时间优化为O(logN),就是在有序数组上做二分查找,寻找满足citations[i] >= len - i 的最小 i 。
思路1:遍历查找 O(N)
和274思路1相同,但省去了排序步骤,所以时间复杂度相比274思路1降为O(N)。
寻找h指数,就是在升序的citations数组中寻找最小的i使citations[i] >= len - i,此时的len-i就是h指数。
class Solution {
public int hIndex(int[] citations) {
for(int i = 0; i < citations.length; i++){
if(citations[i] >= citations.length - i) return citations.length - i;
}
return 0;
}
}
思路2:二分查找 O(logN)
这题要使用二分查找,关键就在于寻找二分查找所依据的不等式,然后根据不等式设计二分查找规则和边界情况处理:
首先,明确查找目标:满足citations[i] >= len - i的最小 i,即如果存在相等的情况,就取相等时的下标 i;如果不存在相等的情况,就取满足citations[i] > len - i 的最小 i。
然后,设计二分查找流程,和常规的二分查找基本相同,只是在退出循环时left,right的处理上有所不同:
取中位点mid=(left+right)/2:
- 如果citations[mid] == len - mid ,返回 len - mid;
- 如果citations[mid] < len - mid,说明 >= len - mid的元素只可能在右半区间,令left=mid+1;
- 如果citations[mid] > len - mid,说明满足 >= len - mid的更小的 i 只可能在左半区间,令right=mid-1;
当left>right时退出循环,说明数组中不存在citations[i] == len - i 的情况,所以此时的left就是满足citations[i]>len-i的最小下标 i,返回len - left就是h指数。
例如:
[0,1,4,5,6] len=5 , h = 3
left=0,right=4, mid=2 nums[mid]=4> 5-2=3,right=1
left=0,right=1, mid=0 nums[mid]=0< 5-0, left=1
left=1,right=1, mid=1,nums[mid]=1< 5-1=4,left=2
left=2>right=1,退出循环。
h = len - left = 5 - 2 = 3.
实现代码:
class Solution {
public int hIndex(int[] citations) {
int len = citations.length;
int left = 0, right = len - 1;
while(left <= right){
int mid = (left + right) / 2;
if(citations[mid] == len - mid) return len - mid;
else if(citations[mid] < len - mid) left = mid + 1;
else if(citations[mid] > len - mid) right = mid - 1;
}
//执行到这里,说明数组中不存在citations[i] == len - i,
//只能寻找满足citations[i]>len-i最小的i,就是退出循环时的left
return len - left;
}
}