题目描述
思路解析
一句话答案: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。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
笨办法谁都会:从引用最多的论文往回数,数到「凑不齐这么多篇都达标」就停。但百万篇论文就慢了。题目特意给了「升序」,就是在提示——能二分。
这是全题的命根子。站在下标 mid 上往右看:右边(含自己)有 n−mid 篇,它们的引用全都 ≥ citations[mid](升序保证)。于是只要 citations[mid] ≥ n−mid,这 n−mid 篇就同时满足「各自 ≥ (n−mid) 次」——h 至少能到 n−mid。
我们其实在找最靠左的那个达标起点:从它到末尾的论文数就是 h。盯住下面 lo/hi/mid 三根指针,看 citations[mid] 和 n−mid 一比,区间怎么一刀刀收缩。最后答案 = n − lo。
理解 n−mid · 站在下标 2 往右看:先把「n−mid」这个量看明白。站在下标 2 上,紫底窗口框住的就是它到末尾的 3 篇(引用 3、5、6)。因为升序,这 3 篇每一篇都 ≥ citations[2]=3。
理解 n−mid · 站在下标 3 往右看:再往右挪到下标 3:窗口缩成2 篇(引用 5、6)。下标越往右,n−mid 越小,但门槛 citations[mid] 越高——这一升一降之间,存在一个最佳平衡点,二分要找的就是它。
理解 n−mid · 站在下标 0 往右看:退到最左下标 0:窗口张到全部 5 篇,可门槛塌到 0——想让 5 篇都 ≥5 次根本办不到。起点太靠左,篇数虽多却没人达标。二分要在「篇数」和「门槛」之间挑出最甜的那个点。
开局 · 二分范围:正式开二分。lo 指数组头(下标 0)、hi 指数组尾(下标 4)。我们要在 [lo,hi] 里夹出那个最靠左的达标起点,循环用 lo ≤ hi,结束时答案 = n − lo。
第1轮 · 取 mid:取中点 mid=(0+4)/2=2。接下来要比的是 citations[mid] 和「从 mid 到末尾的论文数 n−mid」。
第1轮 · 看右边这批:从 mid=2 到末尾框出 3 篇(紫底窗口)。问题变成:这 3 篇能不能各自都 ≥ 3 次?由升序,只要最矮的那篇 citations[mid]=3 过关,全员就都过关。
第1轮 · 比 citations[mid] 与 n−mid:citations[2]=3 ≥ n−mid=3 成立!说明右边这 3 篇确实「各自 ≥ 3 次」,h 至少能到 3。但说不定再往左还能找到更靠前的达标起点(让论文数更多),所以要继续往左探:hi = mid−1 = 1。
第1轮 · 收 hi=mid−1:hi 收到 1,下标 2~4 整段变灰。注意:我们没丢掉这个答案——lo 此刻还停在 0,最靠左达标起点的记录由 lo 兜着。继续在左半 [0,1] 里看能不能找到更靠前的起点。
第1轮 · 当前最佳候选:收完 hi 别忘了:下标 2 这个达标起点(绿色 3 篇)已经入袋,候选 h=3 是保底了。接下来在左半区间 [0,1] 找的,是能不能有更靠前的起点把篇数做大。下一轮见分晓。
第2轮 · 取 mid:新区间 [0,1] 取中点,mid=(0+1)/2=0。再按同一条规则比一次。
第2轮 · 看右边这批:从 mid=0 往右就是全部 5 篇。要让 h=5,得这 5 篇每篇都 ≥ 5 次。可最矮的 citations[0]=0,差得远——这一关多半过不了。
第2轮 · 比 citations[mid] 与 n−mid:citations[0]=0 < n−mid=5 不成立。想要 5 篇各自 ≥5 次办不到。说明达标起点不可能在 mid=0 这么靠左,把 mid 及它左边全砍掉:lo = mid+1 = 1。
第2轮 · 推 lo=mid+1:lo 推到 1,下标 0 变灰出局。现在 lo 和 hi 都指向 1,还满足 lo ≤ hi,再比最后一轮。
第3轮 · 取 mid:区间收成 [1,1],取中点 mid=1。这是最后一搏:看看下标 1 能不能当达标起点。
第3轮 · 看右边这批:从 mid=1 往右框出 4 篇(引用 1、3、5、6)。要 h=4,得这 4 篇每篇都 ≥ 4 次。可最矮的 citations[1]=1 太低了。
第3轮 · 比 citations[mid] 与 n−mid:citations[1]=1 < n−mid=4 不成立。4 篇各自 ≥4 次也办不到。继续砍:lo = mid+1 = 2。这下 lo 越过 hi 了。
第3轮 · 推 lo=mid+1:lo 推到 2,下标 0、1 都变灰出局。此刻 lo=2 已经越过 hi=1,循环条件 lo ≤ hi 破了,二分该停了。lo 最终停的位置就是最靠左的达标起点。
二分结束 · 算答案:lo 最终停在下标 2——这是「能让右边整批都达标」的最靠左起点。从它到末尾共 n − lo = 5 − 2 = 3 篇(绿色高亮的 3、5、6),每篇都 ≥ 3 次。
取出答案:返回 h = n − lo = 3:有 3 篇论文各自至少被引 3 次,这就是 h 指数。5 篇论文只比了 3 轮,全程 O(log n),没有一次多余扫描。
雷区实演 · 全员高引用:换个例子 [1,2,100]:想要 3 篇各自 ≥3 次?citations[0]=1 不够,起点右移;下标 1 往右 2 篇(2、100)各自 ≥2 次?成立。lo 停在 1,h = 3−1 = 2。注意 100 再大也不能让 h 超过总篇数。
边界三连:三种极端:全 0、单篇高引、单篇 0 引。循环 lo≤hi 配上「n−lo」的收尾,把它们都兜得稳稳的。
面试追问:把「有序才能二分」「citations[mid]≥n−mid 的含义」「lo 为何是答案」讲透,是这题的面试加分项。
参考代码
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 - lo复杂度
- 时间复杂度:O(log n),每轮区间砍半,最多 log n 轮
- 空间复杂度:O(1),只用 lo/hi/mid 三个指针,原地完成
易错点
面试追问把动画讲成自己的话
追问和 LC274(H 指数 I)有什么区别?
追问为什么 citations[mid]≥n−mid 能代表「右边 n−mid 篇都达标」?
追问二分结束时为什么 lo 正好是答案起点?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
有序矩阵中第 K 小的元素
LeetCode 378 · 中等 · 沿着 二分套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题