题干
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 **h 指数。
根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数** 是其中最大的那个。
示例 1:
输入: citations = [3,0,6,1,5]
输出: 3
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。
由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。
示例 2:
输入: citations = [1,3,1]
输出: 1
题解
第一个想到的是先将数组排序。排序后的数组有这样一个性质,第i个位置之后的数都大于等于第i个位置上的数。遍历排序之后的数组,如果当前值比剩余元素数量要大,则取剩余元素数量和当前最大的h指数做比较;如果当前值比剩余元素数量小,则取当前值和当前最大的h指数做比较。也即取当前元素值和剩余元素数量的最小值和当前最大的h指数做比较,可以得到如下算法:
func hIndex(citations []int) int {
sort.Ints(citations)
n := len(citations)
var maxC int
var tmpC int
for idx, c := range citations {
// 如果当前值比剩余元素数量要大,则取剩余元素数量和当前最大值做比较
// 如果当前值比剩余元素数量小,则取当前值和当前最大值做比较
tmpC = min(c, n-idx)
if tmpC > maxC {
maxC = tmpC
}
}
return maxC
}
该方法时间复杂为O(nlogn),空间复杂度为O(n),并不是最优解。
计数排序
重新分析一下h指数的定义,在数组中至少有x个元素大于等于x,满足这个条件的所有x中最大的那个数为h指数。不难看出,h指数的取值范围在[0, len(nums)]之间,由于h指数的取值是有限的,所以可以用计数排序来解决这个问题。设置长度为len(nums)+1的数组counter,counter的第i位的含义是nums中等于i的元素的数量,第len(nums)位的含义是nums中大于等于len(nums)的元素的数量。遍历一遍nums得到counter之后,倒序遍历counter,并用一个变量total记录大于等于i的元素的数量,找到第一个满足total >= i的i即为h指数。
以示例1为例:
func hIndex(citations []int) int {
n := len(citations)
var counter []int = make([]int, n+1)
for _, v := range citations {
if v > n {
counter[n]++
} else {
counter[v]++
}
}
total := 0
for i := n; i >= 0; i-- {
total += counter[i]
// 倒序遍历,当找到第一个满足大于等于i的元素数量至少为i的i,就返回i作为h指数
if total >= i {
return i
}
}
return 0
}
该方法时间复杂度为O(n),空间复杂度也为O(n)
二分查找
除了计数排序之外,还可以使用二分查找。由于本质上我们需要在[0, len(nums)]范围内寻找一个满足h指数定义的数,而这个范围本身是有序的,所以满足使用二分查找的条件。对于每次二分查找的中点mid,遍历一遍数组计算数组中比mid大的元素的个数,如果数组中至少有mid个数比mid大,则要找的h指数可以更大,往右边继续二分;数组中不存在mid个数比mid大,说明mid不满足h指数的定义,真正的h指数会更小,所以往左边二分。
func hIndex(citations []int) int {
n := len(citations)
var left int
var right int = n
var mid int
for left < right {
mid = (left + right + 1) >> 1
cnt := 0
for _, v := range citations {
if v >= mid {
cnt++
}
}
// 数组中至少有mid个数比mid大,则要找的h指数可以更大,往右边继续二分
// 数组中不存在mid个数比mid大,说明mid不满足h指数的定义,真正的h指数会更小,所以往左边二分
if cnt >= mid {
left = mid
} else {
right = mid - 1
}
}
return left
}
该方法时间复杂度为O(nlogn),空间复杂度也为O(1)