coverPiccoverPic

Leetcode 274 H-Index

前言

H-Index,这是一道 mid 难度的题目,需要我们对给定的作者 paper 引用次数citations计算 h-index,h-index 就是:

The h-index is defined as the maximum value of h such that the given researcher has published at least h papers that have each been cited at least h times.

就是说对于一个 hh,它满足作者至少有 hh 篇文章引用数大于等于 hhh-index\textit{h-index}hh 的最大值。h-index=5\textit{h-index} = 5,则说明作者有至少 5 篇文章引用数大于等于 5,而且并不存在 6 篇引用数大于等于 6 的文章。

动态规划(×)枚举(√)

因为做题的习惯,首先想到的是动态规划,即根据截止到第 i1i - 1 篇文章的 h-index\textit{h-index} 计算到第 ii 篇文章的 h-index\textit{h-index}。事实上计算到第 ii 篇文章的 h-index\textit{h-index} (记为 hih_i)需要用到前面所有文章的引用数:(用伪代码写一下)

delta=sum(citations>hi1)>hi1hi=hi1+deltadelta = sum(citations > h_{i-1}) > h_{i-1}\\ h_i = h_{i-1} + delta

这种动态规划(?)的思路复杂度应该是时间 O(n2)O(n^2),空间 O(1)O(1)nncitations的长度。

也可以换个方向,不枚举下标,而是枚举结果 h-index\textit{h-index},或者说 hh,很显然:

0hcitations.length0 \leq h \leq citations.length

从大到小枚举 hh,也就是找到符合题目条件最大的 hh 值,有:

ts
  1. function hIndex(citations: number[]): number {
  2. const len = citations.length
  3. const calc = function (target: number) {
  4. let count = 0
  5. for (let i = 0; i < len; i++) {
  6. if (citations[i] >= target) count++
  7. }
  8. return count >= target
  9. }
  10. let ans = 0
  11. for (let i = len; i >= 0; i--) {
  12. if (calc(i)) {
  13. ans = i
  14. break
  15. }
  16. }
  17. return ans
  18. };

复杂度应该是时间 O(n2)O(n^2),空间 O(1)O(1)

二分搜索

我们可以用二分搜索优化一下上面的枚举,时间复杂度 O(nlogn)O(nlogn),空间复杂度是 O(1)O(1)

ts
  1. function hIndex(citations: number[]): number {
  2. const len = citations.length
  3. const calc = function (target: number) {
  4. let count = 0
  5. for (let i = 0; i < len; i++) {
  6. if (citations[i] >= target) count++
  7. }
  8. return target - count
  9. }
  10. let left = 0, right = len + 1
  11. while (left < right) {
  12. const mid = (left + right) >> 1
  13. const val = calc(mid)
  14. if (val <= 0) {
  15. left = mid + 1
  16. } else {
  17. right = mid
  18. }
  19. }
  20. return left - 1
  21. };

构造一个 hhcitations离满足 h-index=h\textit{h-index}=h 文章数差额的函数:

f(h)=hsum(citationsh)f(h) = h - sum(citations \geq h)

sum(citationsh)sum(citations \geq h)也就是citations引用大于等于 hh 的文章数,随 hh 递减,因此上面的 f(h)f(h) 是单调递增的,因此可以对其进行从大到小地枚举和二分搜索。

上面的代码就是需要查找这个函数小于 0 的最大值,即满足题目citations的最大 hh,也就是二分查找中说的右侧边界。

空间换时间的优化

我们可以在 O(n)O(n) 时间内把 hf(h)h\rightarrow f(h) 的结果算出来,这样子就可以时间复杂度 O(n)O(n),空间复杂度 O(n)O(n) 了。

ts
  1. function hIndex(citations: number[]): number {
  2. const len = citations.length
  3. const cache = new Array(len + 1).fill(0)
  4. for (let i = 0; i < len; i++) {
  5. cache[Math.min(citations[i], len)]++
  6. }
  7. for (let i = len - 1; i >= 0; i--) {
  8. cache[i] += cache[i + 1]
  9. }
  10. let left = 0, right = len + 1
  11. while (left < right) {
  12. const mid = (left + right) >> 1
  13. const val = mid - cache[mid]
  14. if (val <= 0) {
  15. left = mid + 1
  16. } else {
  17. right = mid
  18. }
  19. }
  20. return left - 1
  21. };

参考这个题解,其实在上面累加cache数组的时候,累加值大于等于i的时候就相当于枚举到了最大的 hh 值,程序可以结束了。

ts
  1. function hIndex(citations: number[]): number {
  2. const len = citations.length
  3. const cache = new Array(len + 1).fill(0)
  4. for (let i = 0; i < len; i++) {
  5. cache[Math.min(citations[i], len)]++
  6. }
  7. let total = 0, ans = 0
  8. for (let i = len; i >= 0; i--) {
  9. total += cache[i]
  10. if (total >= i) {
  11. ans = i
  12. break
  13. }
  14. }
  15. return ans
  16. };

同样地,时间复杂度 O(n)O(n),空间复杂度 O(n)O(n)

总结

本文讨论了基于枚举和二分搜索的 H-Index 解法及其优化。感觉遇到枚举的题目可以考虑进行二分搜索,进一步地,对于搜索中耗时计算可以考虑进行空间换时间的优化。

0 条评论未登录用户
Ctrl or + Enter 评论
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run