leetcode 面试经典 150 题(11/150) 274. H 指数

67 阅读6分钟

题目描述

给定一个整数数组 citations,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 H 指数

H 指数 的定义 (来自维基百科): 一位科研人员的 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

提示:

  • n == citations.length
  • 1 <= n <= 5000
  • 0 <= citations[i] <= 1000

算法思路

核心思想:计数 + 逆序遍历

要计算 H 指数,最核心的是要找到满足定义的最大 h 值。 一个直观的思路是,我们可以尝试不同的 h 值,然后验证是否满足 H 指数的定义。 为了高效地验证,我们可以使用 计数排序 的思想,统计出不同引用次数的论文数量,然后逆序遍历可能的 h 值,快速找到符合条件的最大值。

思考过程:

  1. 理解 H 指数的定义: 至少有 h 篇论文被引用至少 h 次。 例如,如果 H 指数为 3,意味着至少有 3 篇论文引用次数 ≥ 3。

  2. 确定 h 值的范围: H 指数 h 的最大可能值是多少? 如果一个研究者发表了 n 篇论文,那么 H 指数 h 最大不会超过 n。因此,h 的取值范围是 [0, n]

  3. 验证 h: 对于给定的 h 值是否满足条件? 需要统计引用次数 >= h 的论文数量,如果这个数量 >= h,则说明当前的 h 值是有效的。

  4. 高效统计引用次数: 为了快速统计引用次数,可以使用计数排序的思想。创建一个计数数组 papers,其中 papers[i] 存储的是引用次数为 i 的论文数量。 遍历 citations 数组,将每篇论文的引用次数 c 映射到 papers 数组中进行计数。 为了简化处理,可以将引用次数超过论文总数 n 的论文都归类到 papers[n] 中,因为 H 指数最大不会超过 n。这样的计数方式适合 n 较小的情况下使用。

  5. 逆序遍历找最大 h: 由于 H 指数是满足条件的最大值,我们可以从最大的可能值 n 开始,逆序遍历 h 的可能取值 [n, n-1, n-2, ..., 0]。 对于每个 h 值,统计引用次数大于等于 h 的论文总数。由于是逆序计算,第一次遇到满足 “引用次数大于等于 h 的论文总数 ≥ h” 的 h 值,那么这个 h 值就是我们要找的最大 H 指数,直接返回即可。

  6. 处理没有论文的情况: 如果论文数组为空,或者没有找到任何满足条件的 h 值,则 H 指数为 0。

总结算法思路:

  • 初始化:
    • n = len(citations) (获取论文总数)。
    • papers = make([]int, n+1) (创建计数数组 papers,用于统计不同引用次数的论文数量)。
  • 计数引用次数: 遍历 citations 数组,统计每种引用次数的论文数量,并将计数结果存储在 papers 数组中。 对于引用次数超过 n 的论文,计数在 papers[n] 中。
  • 逆序遍历并验证 h 值:h = n 逆序遍历到 h = 0
    • 累加 papers 数组中引用次数大于等于当前 h 值的论文数量 (count)。
    • 如果 count >= h,则说明当前 h 值满足 H 指数的定义,直接返回 h
  • 返回 0: 如果循环结束都没有找到满足条件的 h 值,则返回 0 (表示 H 指数为 0)。

复杂度分析

  • 时间复杂度: O(n),其中 n 是 citations 数组的长度。 主要的时间消耗在遍历 citations 数组进行计数,以及逆序遍历 papers 数组。 两个遍历都是线性时间复杂度。
  • 空间复杂度: O(n),主要的空间消耗在创建计数数组 papers,其长度为 n+1,与论文数量 n 成正比。

代码实现

func hIndex(citations []int) int {
    n := len(citations)         // 最大的 h 值不会超过 n
    papers := make([]int, n+1) // papers[i] 表示引用次数为 i 的论文数量

    // 统计各引用次数的论文数量
    for _, c := range citations {
        if c >= n {
            papers[n]++ // 引用次数超过 n 的都算作 n
        } else {
            papers[c]++
        }
    }

    count := 0
    for i := n; i >= 0; i-- {
        count += papers[i] // 引用次数大于等于 i 的论文总数
        if count >= i {    // 找到满足 h 指数定义的 h 值
            return i
        }
    }

    return 0 // 如果没有论文,则 H 指数为 0
}

示例解析

示例 1: citations = [3,0,6,1,5]

  1. 初始化: n = 5, papers = [0, 0, 0, 0, 0, 0] (长度为 6)
  2. 计数引用次数:
    • citations[0] = 3papers[3]++papers = [0, 0, 0, 1, 0, 0]
    • citations[1] = 0papers[0]++papers = [1, 0, 0, 1, 0, 0]
    • citations[2] = 6citations[2] >= npapers[5]++papers = [1, 0, 0, 1, 0, 1]
    • citations[3] = 1papers[1]++papers = [1, 1, 0, 1, 0, 1]
    • citations[4] = 5citations[4] >= npapers[5]++papers = [1, 1, 0, 1, 0, 2]
  3. 逆序遍历并验证 h 值:
    • i = 5: count = papers[5] = 2count < i (2 < 5),不满足条件。
    • i = 4: count = papers[5] + papers[4] = 2 + 0 = 2count < i (2 < 4),不满足条件。
    • i = 3: count = papers[5] + papers[4] + papers[3] = 2 + 0 + 1 = 3count >= i (3 >= 3),满足条件,返回 h = 3

示例 2: citations = [1,3,1]

  1. 初始化: n = 3, papers = [0, 0, 0, 0] (长度为 4)
  2. 计数引用次数: papers = [0, 2, 0, 1] (计算过程略)
  3. 逆序遍历并验证 h 值:
    • i = 3: count = papers[3] = 1count < i (1 < 3),不满足条件。
    • i = 2: count = papers[3] + papers[2] = 1 + 0 = 1count < i (1 < 2),不满足条件。
    • i = 1: count = papers[3] + papers[2] + papers[1] = 1 + 0 + 2 = 3count >= i (3 >= 1),满足条件,返回 h = 1

关键点

  • H 指数的定义理解: 理解 H 指数的定义。
  • 计数排序思想: 使用计数数组降低复杂度。
  • 逆序遍历: 逆序遍历 h 的可能取值,能够快速找到最大的满足条件的 H 指数。
  • 边界处理: 处理引用次数超过论文总数的情况,以及没有论文的情况。