【刷题笔记】H指数||数组||二分查找的变体

H指数

最新编辑于2023.11.29
之前的代码写得有点抽象,实在抱歉,好像我自己都不理解当时自己怎么写的,现在重新更新了代码,保证好理解。

1 题目描述

https://leetcode.cn/problems/h-index/

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 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


2 思路

反正不是我想出来的,随便写写

解题方法来源

这道题也是一道“翻译不当人”的典范,我们可以将这句话重新翻译一下

找到一个阈值h,数组中大于等于h的元素数量也大于等于h。同时保证阈值为h+1的时候,找不到足够数量的元素个数。

然后我们更加格式化地理解一下上面这句话:假设有一个统计函数counter(c)来统计数组中大于等于c的元素个数,counter(h) >= h, counter(h+1) < h+1

比如我们设citations=[13, 7, 6, 18, 12, 20, 20, 3, 0, 7],我们用gif的形式来表现当c=1、2、3...10的时候,数组中满足条件的元素个数。

在这里插入图片描述

counter(1) = 9 >= 1
counter(2) = 9 >= 2
counter(3) = 9 >= 3
counter(4) = 8 >= 4
counter(5) = 8 >= 5
counter(6) = 8 >= 6
counter(7) = 7 >= 7
-------------------
counter(8) = 5 < 8
counter(9) = 5 < 9
counter(10) = 5 < 10

我们用下图表示:

在这里插入图片描述

假设我们有一个数组[1, 2, ... , len(citiations)](设为nums),其中一定有一个分界线h,左和右分别表示满足或者不满足条件。

而当我们将上述数组替换成counter(c),即counters

在这里插入图片描述

很明显,counters数组是一个有序的、递减的数组。

在这里插入图片描述

好像事情朝着有趣的方向发展了,我们有一个有序数组counters,我们有一个判定条件counters[i] >= i+1(i在这里是下标),我们需要从有序数组中找到最后一个符合条件的元素

But, 如果我们对[1, 2, ... , len(citiations)](设为nums)数组进行遍历,对每个元素都施加counter函数来获得最终的counters数组,太耗时了。我们看看,能不能对上面的表述进行一些改造,毕竟nums和counters是一一对应的,我们可以在遍历到nums对应的元素时,再计算对应的counter:

我们有一个有序数组[1, 2, … , len(citiations)](表示为nums),我们有一个判定条件counter(nums[i]) >= i+1(i在这里是下标),我们需要从有序数组中找到最后一个符合条件的元素

在二分法中,我比较喜欢直接使用第1个元素、第2个元素、……、第n个元素这样的方式来指示元素的位置。

二分法中具有两个关键问题:

  • mid元素的下标计算
  • left指针和right指针的跳转方式

mid的计算,其实和left以及right指针的跳转方式相关。比如我们这个题目中,需要找到最后一个符合条件的元素。

也就是说,假设counter(nums[mid]) >= nums[mid],我们知道nums[mid]满足条件,但是nums[mid+1]还满足吗?不一定!所以left就 不能 直接跳转到mid的右边,而是 只能 直接跳转到mid上。

 if (counter(cs, nums[mid_index]) >= nums[mid_index]) {
        l = real_mid;
    } else {
        r = real_mid - 1;
    }

在这里插入图片描述

那么我们如何计算mid呢?这就需要我们考虑当只剩下两个元素的时候,mid到底是在left上还是在right上。如果让mid=left,下一步如果left需要跳转,left=mid,然后mid=left。。。。。。无限循环。

所以我们先看怎么跳转,再回过头想怎么计算mid。

也就是说,如果当前的[left~right]之间的元素个数为偶数,我们需要让mid指针指向中间两个元素的后一个元素。

class Solution {
    public int hIndex(int[] cs) {
        int[] nums = new int[cs.length];
        for (int i = 0; i < cs.length; i++) {
            nums[i] = i + 1;
        }
        int l = 1;
        int r = cs.length;

        while (l < r) {
            int real_mid = (l + r) / 2 + ((l - r + 1) % 2 == 0 ? 1 : 0); 
            // 如果l~r的元素个数为奇数个,(l+r) / 2 就是中间元素的真实位置
            // 如果l-r的元素个数为偶数个,(l+r) / 2 就是中间两个的元素的靠左的元素,所以要+1
            // 变成中间两个元素靠右的位置。
            int mid_index = real_mid - 1;
            if (counter(cs, nums[mid_index]) >= nums[mid_index]) {
                l = real_mid; // left转移到mid
            } else {
                r = real_mid - 1; // right转移到mid - 1
            }
        }
        return counter(cs, l) >= l ? l : 0; // 有两种情况,一个是cs中只有一个元素,另一种情况是所有元素都为0

    }
    int counter(int[] cs, int mid) {
        int ans = 0;
        for (int i : cs) if (i >= mid) ans++; // 计算citiations中大于等于mid的元素数量。
        return ans;
    }
}

在这里插入图片描述

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值