LeetCode 274.H指数【中等】

79 阅读4分钟

题干

给你一个整数数组 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的数组countercounter的第i位的含义是nums中等于i的元素的数量,第len(nums)位的含义是nums中大于等于len(nums)的元素的数量。遍历一遍nums得到counter之后,倒序遍历counter,并用一个变量total记录大于等于i的元素的数量,找到第一个满足total >= ii即为h指数。 以示例1为例:

计数排序.png

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)