H 指数 II 图解题解
这道题到底在问什么
- 输入
- [0,1,3,5,6]
- 输出
- 3
先想最直接的笨办法
笨办法谁都会:从引用最多的论文往回数,数到「凑不齐这么多篇都达标」就停。但百万篇论文就慢了。题目特意给了「升序」,就是在提示——能二分。(动画第 3 步)
最优解:为什么这么做
一句话答案:LeetCode 275 H 指数 II(h 篇论文各被引 ≥ h 次的最大 h):引用数升序,二分找第一个 citations[mid] ≥ n−mid 的下标,答案 = n − 它,有序让达标性质单调可砍半,时间 O(log n)。
先说清 h 指数是什么,这题又求什么
h 指数是学者影响力的刻度:有 h 篇论文、每篇都至少被引用 h 次,这个 h 就成立,要的是能成立的最大 h。给的数组 citations 已升序(从小到大排好),citations[i] 是第 i 篇论文的被引次数。题面 citations=[0,1,3,5,6],答案 h=3,引用 3、5、6 这 3 篇各 ≥ 3 次,凑 4 篇 ≥ 4 就不够。题目卡死 O(log n)、不许挨篇数。
从引用最多的往回数,为什么大数组会卡住
一个数法是从引用最多的往回数:数到第 k 篇就看这 k 篇是不是都 ≥ k 次,凑不齐就停。最坏从头扫到尾、比满 n 次,是 O(n)。论文上百万就走不动,而题目点名 O(log n),把逐篇数堵死。
citations[mid] 和 n−mid 一比,分界就现出来
站在下标 mid 往右看,从它到末尾共 n−mid 篇,升序保证这批引用全都 ≥ citations[mid]。只要最矮的 citations[mid] ≥ n−mid,这 n−mid 篇就个个 ≥ n−mid 次,h 至少能到 n−mid。往右挪下标时 citations[mid] 不减、n−mid 却递减,「citations[mid] ≥ n−mid 成立吗」这个判断沿下标从不成立翻成成立、翻过去不再回头。这条单调翻转就是能二分的凭据,要找第一个成立的下标。
达标往左收、不够往右跳,答案为什么读 n − lo
用 lo、hi 圈住还没排除的下标,起手 lo=0、hi=n−1,两端算数(闭区间)。每轮取中点 mid=(lo+hi)//2 比 citations[mid] 和 n−mid:达标(≥)说明从 mid 起够格,但更靠左也许有达标起点让篇数更多,hi 退到 mid−1 往左探;不达标(<),mid 及左边都凑不齐,lo 跳到 mid+1。循环 lo ≤ hi,缩到 lo 越过 hi 停。此时 lo 停在第一个达标下标,从它到末尾有 n − lo 篇,返回 n − lo、不是 lo。
拿 [0,1,3,5,6] 逐轮列出 lo、mid、hi 和判定
n=5,lo=0、hi=4。第一轮 mid=(0+4)//2=2,citations[2]=3,n−mid=3,3 ≥ 3 达标,hi 退到 1。第二轮 mid=(0+1)//2=0,citations[0]=0,n−mid=5,0 < 5 不达标,lo 跳到 1。第三轮 lo=hi=1,mid=1,citations[1]=1,n−mid=4,1 < 4 不达标,lo 跳到 2。此时 lo=2 越过 hi=1,循环停,返回 5−2=3。
方向一记反,二分就找错那头
每轮区间砍半,最多 log n 轮,时间 O(log n);只用 lo、hi、mid 三个指针,空间 O(1)。方向最容易记反:达标时该收 hi 往左找更靠前的起点,写成 lo=mid+1 就掉了头、起点全偏。收尾别返回 lo:它是达标起点下标,要的是到末尾的篇数 n − lo,返回 lo 等于把下标当篇数交出去。mid 也别写 (lo+hi)//2,lo+hi 会溢出,用 lo+(hi−lo)//2。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3笨办法谁都会:从引用最多的论文往回数,数到「凑不齐这么多篇都达标」就停。但百万篇论文就慢了。题目特意给了「升序」,就是在提示——能二分。
- 4这是全题的命根子。站在下标 mid 上往右看:右边(含自己)有 n−mid 篇,它们的引用全都 ≥ citations[mid](升序保证)。于是只要 citations[mid] ≥ n−mid,这 n−mid 篇就同时满足「各自 ≥ (n−mid) 次」——h 至少能到 n−mid。
- 5我们其实在找最靠左的那个达标起点:从它到末尾的论文数就是 h。盯住下面 lo/hi/mid 三根指针,看 citations[mid] 和 n−mid 一比,区间怎么一刀刀收缩。最后答案 = n − lo。
- 6下标 2 到末尾共 5−2=3 篇先把「n−mid」这个量看明白。站在下标 2 上,紫底窗口框住的就是它到末尾的 3 篇(引用 3、5、6)。因为升序,这 3 篇每一篇都 ≥ citations[2]=3。
- 7下标 3 到末尾共 5−3=2 篇再往右挪到下标 3:窗口缩成2 篇(引用 5、6)。下标越往右,n−mid 越小,但门槛 citations[mid] 越高——这一升一降之间,存在一个最佳平衡点,二分要找的就是它。
- 8下标 0 到末尾共 5−0=5 篇退到最左下标 0:窗口张到全部 5 篇,可门槛塌到 0——想让 5 篇都 ≥5 次根本办不到。起点太靠左,篇数虽多却没人达标。二分要在「篇数」和「门槛」之间挑出最甜的那个点。
- 9lo=0, hi=n−1=4正式开二分。lo 指数组头(下标 0)、hi 指数组尾(下标 4)。我们要在 [lo,hi] 里夹出那个最靠左的达标起点,循环用 lo ≤ hi,结束时答案 = n − lo。
- 10mid = (0+4)/2 = 2取中点 mid=(0+4)/2=2。接下来要比的是 citations[mid] 和「从 mid 到末尾的论文数 n−mid」。
- 11mid=2 → 右边 n−mid=3 篇从 mid=2 到末尾框出 3 篇(紫底窗口)。问题变成:这 3 篇能不能各自都 ≥ 3 次?由升序,只要最矮的那篇 citations[mid]=3 过关,全员就都过关。
- 12citations[2]=3 ≥ n−mid=3 ✓citations[2]=3 ≥ n−mid=3 成立!说明右边这 3 篇确实「各自 ≥ 3 次」,h 至少能到 3。但说不定再往左还能找到更靠前的达标起点(让论文数更多),所以要继续往左探:hi = mid−1 = 1。
- 13记下候选,hi → 1hi 收到 1,下标 2~4 整段变灰。注意:我们没丢掉这个答案——lo 此刻还停在 0,最靠左达标起点的记录由 lo 兜着。继续在左半 [0,1] 里看能不能找到更靠前的起点。
- 14h ≥ 3 已锁底,看左半能否更优收完 hi 别忘了:下标 2 这个达标起点(绿色 3 篇)已经入袋,候选 h=3 是保底了。接下来在左半区间 [0,1] 找的,是能不能有更靠前的起点把篇数做大。下一轮见分晓。
- 15mid = (0+1)/2 = 0新区间 [0,1] 取中点,mid=(0+1)/2=0。再按同一条规则比一次。
- 16mid=0 → 右边 n−mid=5 篇从 mid=0 往右就是全部 5 篇。要让 h=5,得这 5 篇每篇都 ≥ 5 次。可最矮的 citations[0]=0,差得远——这一关多半过不了。
- 17citations[0]=0 < n−mid=5 ✗citations[0]=0 < n−mid=5 不成立。想要 5 篇各自 ≥5 次办不到。说明达标起点不可能在 mid=0 这么靠左,把 mid 及它左边全砍掉:lo = mid+1 = 1。
- 18排除下标 0,lo → 1lo 推到 1,下标 0 变灰出局。现在 lo 和 hi 都指向 1,还满足 lo ≤ hi,再比最后一轮。
- 19mid = (1+1)/2 = 1区间收成 [1,1],取中点 mid=1。这是最后一搏:看看下标 1 能不能当达标起点。
- 20mid=1 → 右边 n−mid=4 篇从 mid=1 往右框出 4 篇(引用 1、3、5、6)。要 h=4,得这 4 篇每篇都 ≥ 4 次。可最矮的 citations[1]=1 太低了。
- 21citations[1]=1 < n−mid=4 ✗citations[1]=1 < n−mid=4 不成立。4 篇各自 ≥4 次也办不到。继续砍:lo = mid+1 = 2。这下 lo 越过 hi 了。
- 22lo → 2,越过 hi=1lo 推到 2,下标 0、1 都变灰出局。此刻 lo=2 已经越过 hi=1,循环条件 lo ≤ hi 破了,二分该停了。lo 最终停的位置就是最靠左的达标起点。
- 23lo=2 → h = n − lo = 5 − 2lo 最终停在下标 2——这是「能让右边整批都达标」的最靠左起点。从它到末尾共 n − lo = 5 − 2 = 3 篇(绿色高亮的 3、5、6),每篇都 ≥ 3 次。
- 24h = 3返回 h = n − lo = 3:有 3 篇论文各自至少被引 3 次,这就是 h 指数。5 篇论文只比了 3 轮,全程 O(log n),没有一次多余扫描。
- 28[1,2,100] → h=2换个例子 [1,2,100]:想要 3 篇各自 ≥3 次?citations[0]=1 不够,起点右移;下标 1 往右 2 篇(2、100)各自 ≥2 次?成立。lo 停在 1,h = 3−1 = 2。注意 100 再大也不能让 h 超过总篇数。
⚠️ 容易写错的地方
✗ 错:把方向记反:≥ 时往右、< 时往左
✓ 对:≥ 时 hi=mid−1(往左)、< 时 lo=mid+1(往右)
我们找的是「最靠左的达标起点」:当前够格说明还能往左挪起点看能不能更多,所以收 hi
✗ 错:最后返回 mid 或 lo 本身
✓ 对:返回 n − lo
lo 是达标起点的下标,从它到末尾的论文数才是 h,即 n−lo
完整代码(Python / C++ / Java)
Python
class Solution:
def hIndex(self, citations):
n = len(citations)
lo, hi = 0, n - 1
while lo <= hi:
mid = (lo + hi) // 2
if citations[mid] >= n - mid: # 右边这批够格 → 往左找更靠前的起点
hi = mid - 1
else: # 凑不齐 → 起点右移
lo = mid + 1
return n - loC++
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = (int)citations.size();
int lo = 0, hi = n - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (citations[mid] >= n - mid) // 够格 → 往左
hi = mid - 1;
else
lo = mid + 1; // 凑不齐 → 往右
}
return n - lo;
}
};Java
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int lo = 0, hi = n - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (citations[mid] >= n - mid) // 够格 → 往左
hi = mid - 1;
else
lo = mid + 1; // 凑不齐 → 往右
}
return n - lo;
}
}复杂度
时间复杂度
O(log n)
每轮区间砍半,最多 log n 轮
空间复杂度
O(1)
只用 lo/hi/mid 三个指针,原地完成
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 H 指数 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
和 H 指数 I(LC274)比,这题凭什么能做到 O(log n)?+
两题的 h 指数定义一模一样,差别只在数组有没有序。LC274 的 citations 是乱的,达标性质左右没规律,只能 O(n)——要么排序后扫一遍,要么用计数桶。本题 LC275 数组已经升序,多了「有序」这一个条件,citations[mid] 随下标不减、n−mid 随下标递减,判断就有了单调性,于是能砍半、压到 O(log n)。
为什么只比最矮的 citations[mid],就敢说右边 n−mid 篇全达标?+
靠的是升序。下标 ≥ mid 的每一篇,引用都 ≥ citations[mid],也就是这批里 citations[mid] 是最矮的一根。既然最矮的都 ≥ n−mid(篇数),比它高的自然也都 ≥ n−mid。于是这 n−mid 篇个个被引 ≥ n−mid 次,恰好凑成「n−mid 篇各 ≥ n−mid 次」,h 至少到 n−mid。最矮的那根一过关,整批就都过关,不用逐篇验。
循环停下时,为什么 lo 不多不少正好落在答案起点?+
这是「找第一个满足条件的位置」型二分。判断沿下标从不成立翻成成立、翻过去不回头,代码里不成立就 lo=mid+1、成立就 hi=mid−1,循环 lo ≤ hi 结束时,lo 恰好停在第一个让 citations[lo] ≥ n−lo 成立的下标——它左边全被判过不达标、右边(含它)都达标。从这个起点到末尾共 n − lo 篇,就是最大的 h。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 H 指数 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。