力扣解题-274. H 指数

5 阅读8分钟

力扣解题-274. H 指数

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

根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且至少有 h 篇论文的被引用次数都不少于 h 次。如果 h 有多种可能的值,h 指数 是其中最大的那个。

示例 1:

输入:citations = [3,0,6,1,5]

输出:3

解释:研究者总共有 5 篇论文(数组长度=5),各篇论文被引次数依次是 3、0、6、1、5 次。 我们找最大的 h 值: - h=3:需要至少3篇论文被引≥3次 → 被引3、6、5次的3篇论文满足,符合条件; - h=4:需要至少4篇论文被引≥4次 → 仅6、5次的2篇满足,不符合; 因此最大的h值是3。

示例 2:

输入:citations = [1,3,1]

输出:1

解释:研究者有3篇论文,各篇被引1、3、1次。 - h=1:需要至少1篇论文被引≥1次 → 3篇都满足,符合条件; - h=2:需要至少2篇论文被引≥2次 → 仅3次的1篇满足,不符合; 因此h指数是1。

提示:

n == citations.length

1 <= n <= 5000

0 <= citations[i] <= 1000

Related Topics

数组、计数排序、排序


第一次解答

解题思路

核心方法:暴力枚举验证法,遍历所有可能的h值(1到论文总数),逐个验证“是否有至少h篇论文的被引次数≥h”,最终找到符合条件的最大h值。逻辑完全贴合h指数定义,新手易理解,但效率较低。

核心逻辑拆解

h指数的本质是“找一个最大的数字h”,满足两个条件:

  1. 研究者至少有h篇论文(因为总论文数是n,所以h最多是n);
  2. 这h篇论文里,每一篇的被引次数都≥h次。

暴力法的思路就是“挨个试h值”:

  • 先试h=1,看看有没有至少1篇论文被引≥1次;
  • 再试h=2,看看有没有至少2篇论文被引≥2次;
  • 直到试到h=n,找到最大的那个符合条件的h。
具体步骤
  1. 初始化变量maxH = 0,记录当前找到的最大有效h值;
  2. 枚举所有可能的h值:外层循环h从1遍历到citations.length(总论文数,h的最大可能值);
  3. 统计符合条件的论文数:内层循环遍历每一篇论文,数出“被引次数≥当前h值”的论文数量(记为count);
  4. 验证并更新maxH:如果count≥h(说明有至少h篇论文被引≥h次),且h比当前maxH大,就把maxH更新为h;
  5. 返回结果:遍历完所有h值后,maxH就是最终的h指数。
性能说明
  • 时间复杂度:O(n²)(外层枚举h值要循环n次,内层统计论文数也要循环n次)。比如n=5000时,总共要执行2500万次操作,虽然能通过题目测试,但效率不高;
  • 空间复杂度:O(1),只用到几个临时变量,没有额外内存开销;
  • 优势:一步不落地验证h指数的定义,不用记复杂技巧,适合新手理解核心概念。
    public int hIndex(int[] citations) {
        int maxH=0;
        for(int h=1;h<=citations.length;h++){
            int count=0;
            for(int i=0;i<citations.length;i++){
                if(citations[i]>=h)
                    count++;
            }
            if(count>=h && h>maxH){
                maxH=h;
            }
        }
        return maxH;
    }

示例解答

解题思路

解法1:排序法

核心方法:排序 + 线性遍历,先把论文被引次数从小到大排序,利用“有序数组”的特性,快速找到最大的h值。时间效率远高于暴力法,是工程中最常用的解法。

核心逻辑拆解

排序后数组的特点:前面的论文被引次数少,后面的多。比如示例1排序后是[0,1,3,5,6](总论文数n=5)。

我们从第一个论文开始看:

  • 对于排序后第i篇论文(i从0开始),它后面还有n - i篇论文(包括自己);
  • 因为数组是升序的,所以这n - i篇论文的被引次数都≥当前这篇的被引次数;
  • 只要当前这篇论文的被引次数≥n - i,就说明“有n - i篇论文被引≥n - i次”,这就是一个有效的h值,且是最大的(因为我们从前往后找,第一个满足的就是最大的)。
具体步骤(以示例1为例)

示例1排序后:[0,1,3,5,6],n=5

  1. i=0(被引0次):n-i=5 → 0≥5?不满足;
  2. i=1(被引1次):n-i=4 → 1≥4?不满足;
  3. i=2(被引3次):n-i=3 → 3≥3?满足 → 直接返回3(这就是最大的h值)。
代码对应步骤
  1. 获取总论文数n = citations.length
  2. 数组排序:调用Arrays.sort(citations)把数组升序排列;
  3. 遍历找最大h值
    • 遍历每一篇论文的下标i(从0到n-1);
    • 计算“当前论文及后面的论文总数”:n - i
    • 如果当前论文的被引次数≥n - i,直接返回n - i(第一个满足的就是最大h值);
  4. 返回默认值:如果遍历完都没满足的(比如所有论文被引次数都是0),返回0。
性能优势
  • 时间复杂度:O(nlogn)(主要耗时在排序,遍历只需要O(n))。n=5000时,排序仅需约6万次操作,比暴力法的2500万次快得多;
  • 空间复杂度:O(logn)(排序时系统用的栈空间),没有额外申请数组,内存开销小。
        public int hIndex(int[] citations) {
            int n = citations.length;
            Arrays.sort(citations);
            for (int i = 0; i < n; i++) {
                if (citations[i] >= n - i) {
                    return n - i;
                }
            }
            return 0;
        }

解法2:计数排序法

核心方法:计数统计 + 反向累加,利用“h值最大不超过总论文数n”的特点,用计数数组统计各被引次数的论文数量,再反向累加找到最大h值。时间复杂度O(n),是本题的最优解。

核心逻辑拆解

因为h值最大是n(总论文数),所以:

  • 哪怕某篇论文被引1000次(超过n),对h值来说,它的作用和“被引n次”是一样的(毕竟h最多是n);
  • 我们先统计“被引次数为0次、1次、…、n次”的论文各有多少篇(被引>n次的都算成n次);
  • 再从h=n开始往回找:累加“被引≥h次”的论文总数,当累加数≥h时,这个h就是答案。
具体步骤(以示例1为例)

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

  1. 统计论文数
    • 被引0次:1篇(0);
    • 被引1次:1篇(1);
    • 被引3次:1篇(3);
    • 被引5次:1篇(5);
    • 被引>5次(算5次):1篇(6);
    • 计数数组count = [1,1,0,1,0,1](count[0]=1,count[1]=1,count[2]=0,count[3]=1,count[4]=0,count[5]=1);
  2. 反向累加找h
    • h=5:累加count[5]=1 → 1≥5?不满足;
    • h=4:累加count[4]=0 → 总累加=1 → 1≥4?不满足;
    • h=3:累加count[3]=1 → 总累加=2 → 2≥3?不满足;
    • h=2:累加count[2]=0 → 总累加=2 → 2≥2?满足?不,示例1答案是3?这里纠正:反向累加是“从h=n开始,累加count[h] + 之前的累加和”,正确计算:
      • h=5:total=1 → 1<5;
      • h=4:total=1+0=1 → 1<4;
      • h=3:total=1+0+1=2 → 2<3;
      • h=2:total=2+0=2 → 2≥2(满足,但继续找更大的);
      • h=1:total=2+1=3 → 3≥1;
      • h=3是第一个满足的最大h值(重新梳理:反向累加是从大到小,h=3时累加和是count[3]+count[4]+count[5] =1+0+1=2 <3;h=2时累加和=count[2]+count[3]+count[4]+count[5]=0+1+0+1=2 ≥2;但示例1的正确答案是3,核心是“找最大的h”,这里的关键是: 正确的累加逻辑是“被引≥h次的论文总数”,h=3时,被引≥3次的论文是3、5、6 → 共3篇,3≥3,满足; 计数数组的累加应是:h=3时,count[3]+count[4]+count[5] =1+0+1=2?错误,因为6被算成count[5],5被算成count[5]?不,示例1中5≤5,应算count[5],6>5算count[5],3算count[3],所以被引≥3次的是count[3]+count[4]+count[5] =1+0+2=3(修正:6和5都算count[5],所以count[5]=2),此时h=3时total=3≥3,满足。
代码实现
        public int hIndexOptimal(int[] citations) {
            int n = citations.length;
            int[] count = new int[n + 1];
            
            // 统计各被引次数的论文数:被引>n次的都算n次
            for (int c : citations) {
                if (c > n) {
                    count[n]++;
                } else {
                    count[c]++;
                }
            }
            
            // 反向累加:从最大的h开始找
            int total = 0; // 累加“被引≥h次”的论文总数
            for (int h = n; h >= 0; h--) {
                total += count[h];
                if (total >= h) { // 有至少h篇论文被引≥h次
                    return h;
                }
            }
            return 0;
        }
性能优势
  • 时间复杂度:O(n)(两次线性遍历,无排序开销),是本题最快的解法;
  • 空间复杂度:O(n)(计数数组),n=5000时仅占约20KB内存,完全可接受。

总结

  1. 暴力枚举法:完全贴合h指数定义,易理解但效率低(O(n²)),适合新手理解核心逻辑;
  2. 排序法:工程常用,效率较高(O(nlogn)),核心是利用排序后数组的“剩余论文数”快速判断h值;
  3. 计数排序法:理论最优(O(n)),核心是利用h值的取值范围(≤n)简化统计,反向累加找最大h;
  4. 关键技巧:h指数的核心是“找最大的h,使得至少h篇论文被引≥h次”,优先用排序/计数排序利用有序性,避免嵌套循环的低效操作。