1.题目介绍

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

h 指数的定义:h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (n 篇论文中)至少 有 h 篇论文分别被引用了至少 h 次。

请你设计并实现对数时间复杂度的算法解决此问题。

2.题解

2.1 模拟

思路分析

这里使用

代码

class Solution {
public:
int hIndex(vector<int>& citations) {
​ int h = 0;
​ for (int i = citations.size() - 1; i>=0; i--){
​ if (citations[i] > h) h++; //这里要考虑到我加入一篇新论文之后,比较的论文总数是h+1而不是h,
​ else return h;
​ }
​ return h; // 若是所有的论文引用次数,都大于n便移动到此处
}
};

复杂度分析

  • 时间复杂度:O(n) 这里并不满足题目要求!所以我们考虑二分查找思路

2.2 二分查找

思路分析

这里数组已经按升序排列,题目要求要用对数时间复杂度,所以我们就可以直接针对数组进行二分查找。

但是注意这里比较容易错的是,这里是升序排列,所以我们计算已经加入的论文数目时,并不是使用 mid(当前下标) + 1 来计算, 而是使用 len - mid 来进行计算。

原理:比如像结尾:return len - left;

我们使用len - left = 尾下标 + 1 - left;

根据种树原理,尾指针-起始指针+1 = 个数

也就是说:尾下标 - left + 1 就是我们需要的论文个数

同理 if (citations[mid] >= len - mid) 中使用 len - mid计算已经加入的论文个数

(这里的mid是下标,len是总数目,二者相减计算的刚好是相差的数目)

代码

这里len - mid 表示的就是现在所取的论文数目,mid为其所处数组的下标位置

这里的left,right都是按下标来算的,

这里其实上是一个左开右闭区间(left左边节点不成立,right(包括right)右边节点成立)

对于while (left < right)

当left = right - 1 时,

由于int mid = left + (right - left) / 2;,

向下取整 mid = left;

有两种情况:

1.当前mid节点满足条件,right = mid(left) = right - 1; right向左推进一个,与left(mid)重合,此时left左边节点均不成立,right(包含right)右边节点均成立,所以这里所求下标值即为left/right,这里论文的个数即为 len - left/right。

2.当前mid节点不满足条件,left = mid(left) + 1 = right; left向右推进一个,与right(mid+1)重合,所以这里所求下标值即为left/right,这里论文的个数即为 len - left/right。

class Solution {
public:
int hIndex(std::vector<int>& citations) {
int len = citations.size();
int left = 0, right = len;
while (left < right){
int mid = left + (right - left) / 2;
//确保在计算 mid 时不会发生溢出,实际计算和(right+left)/2等效 //这里注意mid的值越大成立越容易
if (citations[mid] >= len - mid) {
right = mid;//mid成立了。根据连续性,右边更大的节点肯定也成立, h <= mid,在(...,mid]中探寻h的值
} else {
left = mid + 1; //mid肯定是不成立了。根据连续性,左边更小的节点也不成立,h > mid,在[mid+1, ...]即(mid,...]中探寻h的值。
}
}
return len - left;
}
};

2.3 通过开闭区间来进行理解

思路分析

1.这里将mid当作当前加入论文的最大数目

if (citations[n - mid] >= mid) ,这里使用 n - mid = 尾指针 + 1 - 数目(包括起始和尾指针) = 尾指针 - [ 数目(包括起始和尾指针)- 1] = 尾指针 - 移动数目 = 起始指针。

总而言之,这里的mid因为包含的是起始->尾部 所有的数目 = 移动数目 + 1; 而 n 恰好为尾指针 +1 两个1抵消。

2. 对于left 和 right 的讨论

对于所处闭区间,使用 left/right = mid;

对于所处开区间,使用 left/right = mid + 1 / mid - 1;

首先是闭区间,结束条件也就是区间不成立的情况:

3.结束条件的讨论:

注意:二分的区间不一定包含答案,答案可能在二分的区间外。

3.1闭区间【left左边的点均成立,right右边的点均不成立】

while (left <= right)(这里的left/right指向的是h指数所在区间,不是指向的引用次数数组)

当到达 left == right 的时候,有两种情况:

1.该mid是成立,left = mid(即left) + 1;

由于right右边所有的点均已证明为不成立,left+1正好指向的是此时right右边的第一个不成立点;

而right指向的点(为left加一之前指向的点)左边的点已证明均成立,

所以实际上要返回的是right所对应的。

2.该mid是不成立,right = mid(即right/left) - 1;

由于left是由成立的mid+1而来,所以left前面所有可能得论文次数都是成立的,

right回退到最大的那个点上,返回right

补充:这里left = h + 1; right = h; 故return left - 1 / return right均可。

且注意这里由于都是闭区间,所以可从[1,n]作为初始区间,至于为何是从1而不是0开始,请看下方解释。

假设最后的mid = 3,下图分别对应两种情况最终位置:

// 闭区间
class Solution {
public:
int hIndex(vector<int> &citations) {
// 在区间 [left, right] 内询问
int n = citations.size();
int left = 1;
int right = n;
while (left <= right) { // 区间不为空
// 循环不变量:
// left-1 的回答一定为「是」
// right+1 的回答一定为「否」
int mid = (left + right) / 2; // left+(right-left)/2
// 引用次数最多的 mid 篇论文,引用次数均 >= mid
if (citations[n - mid] >= mid) {
left = mid + 1; // 询问范围缩小到 [mid+1, right]
} else {
right = mid - 1; // 询问范围缩小到 [left, mid-1]
}
}
// 循环结束后 right 等于 left-1,回答一定为「是」
// 根据循环不变量,right 现在是最大的回答为「是」的数
return right;
}
}; 作者:灵茶山艾府
链接:https://leetcode.cn/problems/h-index-ii/solutions/2504326/tu-jie-yi-tu-zhang-wo-er-fen-da-an-si-ch-d15k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.2左闭右开区间【left左边的点均成立,right(包括right)右边的点均不成立】

while (left < right)(比如像[4,5),由于数目不是小数,故此时已经可以结束循环)

当到达left = right - 1时

由于这里是向下取整, mid == left;

这里也有两种情况

1.该mid是成立,left = mid(left) + 1 = right;

而又因为左闭区间,left左侧所有点均成立均是成立的

由于这里右边是开区间,表明包括从right开始(包括right)的点是不成立的

故h = (left - 1)或 (right - 1);

2.该mid是不成立,right = mid(right - 1);

此时 left == right;

由于这里左闭区间,所以left左边所有的点均成立。

由于这里右开区间,所以right包括right右边所有的点均不成立。

故h = (left - 1)或 (right - 1);

补充:且注意这里由于左闭右开,所以可从[1,n+1) (选用n+1就保证了n在区间内)作为初始区间,至于为何是从1而不是0开始,请看下方解释。

假设最后的mid = 3,下图分别对应两种情况最终位置:

// 左闭右开区间
class Solution {
public:
int hIndex(vector<int> &citations) {
// 在区间 [left, right) 内询问
int n = citations.size();
int left = 1;
int right = n + 1;
while (left < right) { // 区间不为空
// 循环不变量:
// left-1 的回答一定为「是」
// right 的回答一定为「否」
int mid = (left + right) / 2;
// 引用次数最多的 mid 篇论文,引用次数均 >= mid
if (citations[n - mid] >= mid) {
left = mid + 1; // 询问范围缩小到 [mid+1, right)
} else {
right = mid; // 询问范围缩小到 [left, mid)
}
}
// 根据循环不变量,left-1 现在是最大的回答为「是」的数
return left - 1;
}
}; 作者:灵茶山艾府
链接:https://leetcode.cn/problems/h-index-ii/solutions/2504326/tu-jie-yi-tu-zhang-wo-er-fen-da-an-si-ch-d15k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.3左开右闭区间【left(包括left)左边的点均成立,right右边的点均不成立】

while (left < right)

当到达left = right - 1时

如果依旧使用向下取整

int mid = (left + right) / 2; 使得mid == left;

这里只有一种情况:

1.该mid是成立,left = mid(left);left = right - 1;

这里的left依旧等于right - 1,发生无限循环,冲突

2.此时由于mid = left, 而根据左开得知,left指向的点必是成立的,所以mid没有不成立的情况。

如果我们使用向上取整呢?

使用向下取整int mid = (left + right + 1) / 2; 使得mid == right;

这里也有两种情况:

1.该mid是成立,left = mid(right);

而又因为左开区间,left(包括left)左侧所有点均成立均是成立的

由于这里右闭区间,表明包括从right右边所有的点是不成立的

故h = left 或 right;

2.该mid是不成立,right = mid(right) - 1 = left;

而又因为左开区间,left(包括left)左侧所有点均成立均是成立的

由于这里右闭区间,表明包括从right右边所有的点是不成立的

故h = left 或 right;

补充:且注意这里由于左闭右开,所以可从(0,n] (选用0就保证了1在区间内)作为初始区间,至于为何是从1而不是0开始,请看下方解释。

假设最后的mid = 3,下图分别对应两种情况最终位置:

//左开右闭区间
class Solution {
public:
int hIndex(vector<int> &citations) {
// 在区间 (left, right] 内询问
int n = citations.size();
int left = 0;
int right = n;
while (left < right) { // 区间不为空
// 循环不变量:
// left 的回答一定为「是」
// right+1 的回答一定为「否」
int mid = (left + right + 1) / 2; // 保证 mid 在二分区间内
// 引用次数最多的 mid 篇论文,引用次数均 >= mid
if (citations[n - mid] >= mid) {
left = mid; // 询问范围缩小到 (mid, right]
} else {
right = mid - 1; // 询问范围缩小到 (left, mid-1]
}
}
// 根据循环不变量,left 现在是最大的回答为「是」的数
return left;
}
}; 作者:灵茶山艾府
链接:https://leetcode.cn/problems/h-index-ii/solutions/2504326/tu-jie-yi-tu-zhang-wo-er-fen-da-an-si-ch-d15k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.4开区间【left(包括left)左边的点均成立,right(包括right)右边的点均不成立】

while (left + 1 < right) [(3,5)包含4,(3,4)区间内只有小数]

当left = right - 2;时, 有mid == left + 1;

这里也有两种情况

1.该mid是成立,left = mid(left + 1) = right - 1;

而又因为左开区间,left(包含left)左侧所有点均成立均是成立的

由于这里右开区间,right(包括right)右侧所有的点是不成立的

故h = (left)或 (right - 1);

2.该mid是不成立,right = mid(right - 1);

此时 left == right - 1;

由于这里左开区间,所以left(包括left)左边所有的点均成立。

由于这里右开区间,所以right(包括right)右边所有的点均不成立。

故h = (left)或 (right - 1);

假设最后的mid = 3,下图分别对应两种情况最终位置:

//开区间
class Solution {
public:
int hIndex(vector<int> &citations) {
// 在区间 (left, right) 内询问
int n = citations.size();
int left = 0;
int right = n + 1;
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// left 的回答一定为「是」
// right 的回答一定为「否」
int mid = (left + right) / 2;
// 引用次数最多的 mid 篇论文,引用次数均 >= mid
if (citations[n - mid] >= mid) {
left = mid; // 询问范围缩小到 (mid, right)
} else {
right = mid; // 询问范围缩小到 (left, mid)
}
}
// 根据循环不变量,left 现在是最大的回答为「是」的数
return left;
}
}; 作者:灵茶山艾府
链接:https://leetcode.cn/problems/h-index-ii/solutions/2504326/tu-jie-yi-tu-zhang-wo-er-fen-da-an-si-ch-d15k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4.为何从1开始

肯定有0篇论文的引用次数≥0,

对于讠=0的询问,回答必然为「是」

因此二分区间不需要包含0,可以在「1,n」/ (0,n] 中二分。

5.各种情况图览

275.H指数II的更多相关文章

  1. Leetcode之二分法专题-275. H指数 II(H-Index II)

    Leetcode之二分法专题-275. H指数 II(H-Index II) 给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列.编写一个方法,计算出研究者的 h 指数. ...

  2. Java实现 LeetCode 275 H指数 II

    275. H指数 II 给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列.编写一个方法,计算出研究者的 h 指数. h 指数的定义: "h 代表"高 ...

  3. 275 H-Index II H指数 II

    这是 H指数 进阶问题:如果citations 是升序的会怎样?你可以优化你的算法吗? 详见:https://leetcode.com/problems/h-index-ii/description/ ...

  4. [LeetCode] 275. H-Index II H指数 II

    Follow up for H-Index: What if the citations array is sorted in ascending order? Could you optimize ...

  5. [Swift]LeetCode275. H指数 II | H-Index II

    Given an array of citations sorted in ascending order (each citation is a non-negative integer) of a ...

  6. 275. H 指数 II--Leetcode_暴力

    来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/h-index-ii 著作权归领扣网络所有.商业转载请联系官方授权,非商业转载请注明出处. 题目的大意是 ...

  7. 275. H 指数 II--Leetcode_二分

    来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/h-index-ii 著作权归领扣网络所有.商业转载请联系官方授权,非商业转载请注明出处. 题目的大意是 ...

  8. [LeetCode] 274. H-Index H指数

    Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...

  9. [LeetCode] H-Index 求H指数

    Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...

  10. [Swift]LeetCode274.H指数 | H-Index

    Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...

随机推荐

  1. 【笔记整理】使用Session会话保持

    import requests if __name__ == '__main__': # Session对象实现了客户端和服务器端的每次会话保持功能. session = requests.Sessi ...

  2. 华企盾DSC可能涉及的Linux命令

    掌握Linux系统的基本命令 详细使用请参考:Linux命令大全 uname 查看系统信息 wget 文件下载 chmod 改变文件或目录权限 ls 显示指定工作目录下的内容及属性信息 cd 切换当前 ...

  3. python 获取apk 信息

    python 获取apk 信息 1.安装androguard pip install androguard 2.获取apk的相关信息 引入基础信息 from androguard.misc impor ...

  4. tomcat中文乱码怎么解决

    需要修改Tomcat根目录下面的"logging.properties"文件,把所有的encoding=UTF-8的改成encodng=GBK,保存之后,重启Tomcat服务器,就 ...

  5. 简单介绍JDK、JRE、JVM三者区别

    简单介绍JDK vs JRE vs JVM三者区别 文编|JavaBuild 哈喽,大家好呀!我是JavaBuild,以后可以喊我鸟哥,嘿嘿!俺滴座右铭是不在沉默中爆发,就在沉默中灭亡,一起加油学习, ...

  6. Luogu P1654 概率DP

    原题链接 题意 我们面前有一个长度为\(N\)的01序列,位置 \(a_i\) 有 \(p_i\) 的概率是1,否则为0. 序列中,一段长为 \(x\) 的连续1会带来 \(x^3\) 的加分(这段全 ...

  7. 技术解读丨目标检测之RepPoints系列算法

    摘要:本文对anchor-free的目标检测RepPoints系列算法进行梳理,具体包含RepPoints, RepPoints V2, Dense RepPoints. 背景介绍 近两年来,anch ...

  8. 华为云AOM 2.0版本发布

    摘要:AOM作为华为云面向租户的统一运维门户,将在7月1日重磅发布2.0版本. 本文分享自华为云社区<华为云AOM发布2.0版本,3大特性亮相>,作者:华为云PaaS小助手. 6月16日华 ...

  9. 列存Delta表是个什么东东

    摘要:本文从delta表的概念.来历.用法.开启后的影响,delta表数据转移到主表几个方面做了详细的介绍. 本文分享自华为云社区<GaussDB(DWS) 列存delta表的简单介绍>, ...

  10. Error: Could not find or load main class org.elasticsearch.tools.java_version_checker.JavaVersionChecker

    把elasticsearch目录换到不属于root目录的其他目录就行了