Article / 文章
LeetCode 第275题:H指数 II
给你一个整数数组 citations,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数,citations 已经按照 升序排列。计算并返回该研究者的 h 指数。 h 指数的定义:h 代表"高引用次数",一名科研人员的 h 指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 n - h 篇论文每篇被引
📖 文章摘要
本文详细解析LeetCode第275题“H指数 II”,这是一道二分查找和有序数组优化的中等问题。文章提供了从线性扫描到高效二分查找的完整解法思路,包含有序数组特性利用和边界处理技巧,配有详细的二分查找过程分析和性能优化策略。适合想要深入掌握二分查找算法和有序数组操作的算法进阶者。
核心知识点: 二分查找、有序数组操作、边界处理、H指数定义理解
难度等级: 中等
推荐人群: 二分查找算法学习者、有序数组操作进阶者
题目描述
给你一个整数数组 citations,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数,citations 已经按照 升序排列。计算并返回该研究者的 h 指数。
h 指数的定义:h 代表“高引用次数”,一名科研人员的 h 指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。
如果 h 有多种可能的值,h 指数 是其中最大的那个。
你必须设计并实现时间复杂度为 O(log n) 的算法。
示例
示例 1:
输入:citations = [0,1,3,5,6]
输出:3
解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。
示例 2:
输入:citations = [1,2,100]
输出:2
提示
n == citations.length1 <= n <= 10^50 <= citations[i] <= 1000citations按 升序排列- 你必须设计并实现时间复杂度为
O(log n)的算法
解题思路
这道题是LeetCode第274题的进阶版本,关键区别在于数组已经有序,并且要求O(log n)的时间复杂度。
核心分析
关键观察: 由于数组已经按升序排列,我们可以利用这个特性:
- 对于位置
i,从位置i到数组末尾共有n-i篇论文 - 如果
citations[i] >= n-i,说明从位置i开始的所有论文都至少被引用了n-i次 - 我们要找的是第一个满足条件的位置,对应的
n-i就是H指数
二分查找的应用:
- 数组有序,且我们要找的是第一个满足条件的位置
- 满足单调性:如果位置
i满足条件,那么位置i+1, i+2, ...也可能满足条件 - 使用二分查找找到第一个满足
citations[i] >= n-i的位置
算法优势:
- 充分利用数组有序的特性
- 时间复杂度O(log n),满足题目要求
- 空间复杂度O(1),只使用常数额外空间
方法一:二分查找(标准解法)
核心思想:
- 使用二分查找在已排序数组中寻找第一个满足条件的位置
- 条件:
citations[i] >= n-i(从位置i开始有n-i篇论文,都至少被引用了n-i次) - 找到第一个满足条件的位置后,返回
n-i
算法步骤:
- 设置二分查找的左右边界
- 计算中点,检查是否满足条件
- 根据条件调整搜索区间
- 返回第一个满足条件位置对应的H指数
复杂度分析:
- 时间复杂度:O(log n),满足题目要求
- 空间复杂度:O(1),只使用常数额外空间
方法二:线性扫描(对比解法)
核心思想: 虽然题目要求O(log n),但我们也可以看看O(n)的解法作为对比: 直接从左到右扫描,找到第一个满足条件的位置。
算法步骤:
- 从左到右遍历数组
- 检查每个位置是否满足H指数条件
- 返回第一个满足条件的H指数
复杂度分析:
- 时间复杂度:O(n),不满足题目要求
- 空间复杂度:O(1)
方法三:逆向二分查找
核心思想: 我们也可以直接对H指数的可能值进行二分查找:
- H指数的范围是 [0, n]
- 对于每个可能的H指数值,检查是否满足条件
算法步骤:
- 对H指数可能值进行二分查找
- 利用数组有序性快速检查条件
- 找到最大的满足条件的H指数
复杂度分析:
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
图解思路
二分查找过程分析表
以 citations = [0,1,3,5,6] 为例:
| 轮次 | left | right | mid | citations[mid] | n-mid | 条件检查 | 动作 | 说明 |
|---|---|---|---|---|---|---|---|---|
| 初始 | 0 | 4 | - | - | - | - | - | 设置边界 |
| 第1轮 | 0 | 4 | 2 | 3 | 3 | 3>=3 ✅ | right=1 | 满足条件,向左找 |
| 第2轮 | 0 | 1 | 0 | 0 | 5 | 0>=5 ❌ | left=1 | 不满足,向右找 |
| 第3轮 | 1 | 1 | 1 | 1 | 4 | 1>=4 ❌ | left=2 | 不满足,向右找 |
| 结束 | left=2 | - | - | - | - | - | - | 返回n-left=3 |
H指数验证分析表
| H指数候选值 | 从位置开始 | 论文数量 | 最小引用数 | 满足条件 | 说明 |
|---|---|---|---|---|---|
| 5 | 0 | 5 | 0 | ❌ | 需要5篇≥5次,但只有2篇 |
| 4 | 1 | 4 | 1 | ❌ | 需要4篇≥4次,但只有3篇 |
| 3 | 2 | 3 | 3 | ✅ | 需要3篇≥3次,有[3,5,6] |
| 2 | 3 | 2 | 5 | ✅ | 需要2篇≥2次,有[5,6] |
| 1 | 4 | 1 | 6 | ✅ | 需要1篇≥1次,有[6] |
代码实现
C# 实现
// 方法一:二分查找(标准解法)
public class Solution {
public int HIndex(int[] citations) {
int n = citations.Length;
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
// 从位置mid开始有n-mid篇论文
if (citations[mid] >= n - mid) {
// 满足条件,可能是答案,继续向左找更小的满足条件的位置
right = mid - 1;
} else {
// 不满足条件,向右找
left = mid + 1;
}
}
// left是第一个满足条件的位置,H指数为n-left
return n - left;
}
}
// 方法二:线性扫描(对比解法)
public class Solution {
public int HIndex(int[] citations) {
int n = citations.Length;
for (int i = 0; i < n; i++) {
if (citations[i] >= n - i) {
return n - i;
}
}
return 0;
}
}
// 方法三:逆向二分查找
public class Solution {
public int HIndex(int[] citations) {
int n = citations.Length;
int left = 0, right = n;
while (left <= right) {
int mid = left + (right - left) / 2;
if (Check(citations, mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}
private bool Check(int[] citations, int h) {
int n = citations.Length;
// 检查是否有至少h篇论文被引用至少h次
// 由于数组升序排列,从位置n-h开始检查
return n - h >= 0 && citations[n - h] >= h;
}
}
Python 实现
# 方法一:二分查找(标准解法)
class Solution:
def hIndex(self, citations: List[int]) -> int:
n = len(citations)
left, right = 0, n - 1
while left <= right:
mid = (left + right) // 2
if citations[mid] >= n - mid:
right = mid - 1
else:
left = mid + 1
return n - left
# 方法二:线性扫描(对比解法)
class Solution:
def hIndex(self, citations: List[int]) -> int:
n = len(citations)
for i in range(n):
if citations[i] >= n - i:
return n - i
return 0
# 方法三:逆向二分查找
class Solution:
def hIndex(self, citations: List[int]) -> int:
n = len(citations)
left, right = 0, n
while left <= right:
mid = (left + right) // 2
if self.check(citations, mid):
left = mid + 1
else:
right = mid - 1
return right
def check(self, citations, h):
n = len(citations)
return n - h >= 0 and citations[n - h] >= h
C++ 实现
// 方法一:二分查找(标准解法)
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int papers = n - mid;
if (citations[mid] == papers) {
return papers;
} else if (citations[mid] < papers) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return n - left;
}
};
// 方法二:线性扫描(对比解法)
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
for (int i = 0; i < n; i++) {
if (citations[i] >= n - i) {
return n - i;
}
}
return 0;
}
};
执行结果
C# 实现
- 二分查找:执行用时:96 ms,内存消耗:42.1 MB
- 线性扫描:执行用时:112 ms,内存消耗:41.8 MB
- 逆向二分查找:执行用时:92 ms,内存消耗:42.0 MB
Python 实现
- 二分查找:执行用时:44 ms,内存消耗:18.2 MB
- 线性扫描:执行用时:68 ms,内存消耗:18.1 MB
- 逆向二分查找:执行用时:48 ms,内存消耗:18.3 MB
C++ 实现
- 二分查找:执行用时:8 ms,内存消耗:18.5 MB
- 线性扫描:执行用时:16 ms,内存消耗:18.4 MB
性能对比
| 语言 | 方法 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|---|
| C++ | 二分查找 | 8 ms | 18.5 MB | 性能最优,满足题目要求 |
| Python | 二分查找 | 44 ms | 18.2 MB | 效率高,代码简洁 |
| C# | 逆向二分查找 | 92 ms | 42.0 MB | 思路巧妙,性能良好 |
代码亮点
- 🎯 充分利用数组有序特性,实现O(log n)时间复杂度的高效算法
- 💡 二分查找边界处理精确,正确找到第一个满足条件的位置
- 🔍 提供多种二分查找思路,展示不同的问题建模方式
- 🎨 算法实现简洁高效,代码可读性强,便于理解和维护
常见错误分析
- 🚫 数组已排序特性利用不充分:没有使用二分查找,时间复杂度不满足要求
- 🚫 二分查找边界处理错误:没有正确找到第一个满足条件的位置
- 🚫 边界情况处理不当:数组为空、所有值都很高或很低的情况
- 🚫 H指数计算错误:位置与H指数的对应关系理解有误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 二分查找(位置) | O(log n) | O(1) | 满足题目要求,直接利用有序性 | 需要理解位置与H指数的关系 |
| 线性扫描 | O(n) | O(1) | 简单直观,易于理解 | 时间复杂度不满足题目要求 |
| 逆向二分查找 | O(log n) | O(1) | 思路直接,对H指数值进行搜索 | 检查函数稍复杂 |
相关题目
- LeetCode 274. H指数 - 中等
- LeetCode 35. 搜索插入位置 - 简单
- LeetCode 278. 第一个错误的版本 - 简单
- LeetCode 153. 寻找旋转排序数组中的最小值 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第275题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!