|
| 1 | +11.H指数 |
| 2 | +=== |
| 3 | + |
| 4 | + |
| 5 | +### 题目 |
| 6 | + |
| 7 | +给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 |
| 8 | + |
| 9 | +根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。 |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +示例 1: |
| 14 | + |
| 15 | +输入:citations = [3,0,6,1,5] |
| 16 | +输出:3 |
| 17 | +解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。 |
| 18 | + 由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。 |
| 19 | +示例 2: |
| 20 | + |
| 21 | +输入:citations = [1,3,1] |
| 22 | +输出:1 |
| 23 | + |
| 24 | +### 思路 |
| 25 | + |
| 26 | +翻译: 数组中有h个不小于h的值,求最大的h |
| 27 | + |
| 28 | + |
| 29 | +至少有h篇论文,每一篇至少被引用次数为h,听起来很绕,但是以用例[0, 1, 3, 5, 6]来分析: |
| 30 | + |
| 31 | +- h指数无非就是5或4或3或2或1或0 |
| 32 | + |
| 33 | +- 那么如果数组中最小的引用次数都大于等于5,那么其他的不用看了,h就是取最大值5 |
| 34 | + |
| 35 | +- 如果最小值不满足,那么如果数组中倒数第二小值如果大于等于4,那么一定有4个是大于等于4的 |
| 36 | + |
| 37 | +- 这就是h指数 |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +##### 方法一:排序 |
| 44 | + |
| 45 | +h肯定不会超过数组的长度。 |
| 46 | + |
| 47 | +[0, 1, 3, 5, 6] |
| 48 | + |
| 49 | +- 从小到大排序。 |
| 50 | +- 排序后从头开始遍历,如果最小的值,都大于数组的size,那就是size |
| 51 | +- 如果上面不满足,那第二小的值如果大于数组的size - 1,那就是size - 1 |
| 52 | +- 所以遍历条件就是 for (int i = 0; i < size; i ++ ) { if (nums[i] >= size - 1)} |
| 53 | +- 遍历排序后的数组,如果数组中该位置的值>h,那就h++然后继续遍历下一个 |
| 54 | + |
| 55 | +```java |
| 56 | +public int hIndex(int[] citations) { |
| 57 | + Arrays.sort(citations); |
| 58 | + for(int i = 0; i < citations.length; i++){ |
| 59 | + if( citations[i] >= (citations.length -i)){ |
| 60 | + return citations.length -i; |
| 61 | + } |
| 62 | + } |
| 63 | + return 0; |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | + |
| 68 | +最终的时间复杂度与排序算法的时间复杂度有关 |
| 69 | + |
| 70 | +复杂度分析 |
| 71 | + |
| 72 | +- 时间复杂度:O(nlogn),其中 n 为数组 citations 的长度。即为排序的时间复杂度。 |
| 73 | + |
| 74 | +- 空间复杂度:O(logn),其中 n 为数组 citations 的长度。即为排序的空间复杂度。 |
| 75 | + |
| 76 | + |
| 77 | +##### 方法二:计数排序 |
| 78 | + |
| 79 | +根据上述解法我们发现,最终的时间复杂度与排序算法的时间复杂度有关。 |
| 80 | + |
| 81 | +所以我们可以使用计数排序算法,新建并维护一个数组 counter 用来记录当前引用次数的论文有几篇。它的值可以是0 ~ n,所以数组的长度是n + 1 |
| 82 | + |
| 83 | +[0, 1, 3, 5, 6] |
| 84 | + |
| 85 | + |
| 86 | +- 我们遍历数组 citations,将引用次数大于 n 的论文都当作引用次数为 n 的论文,然后将每篇论文的引用次数作为下标,将 cnt 中对应的元素值加 1。这样我们就统计出了每个引用次数对应的论文篇数。 |
| 87 | + |
| 88 | +``` |
| 89 | +counter[0] = 1 |
| 90 | +counter[1] = 1 |
| 91 | +counter[2] = 0 |
| 92 | +counter[3] = 1 |
| 93 | +// 无值,默认0 |
| 94 | +counter[4] = 0 |
| 95 | +// 引用次数为5的论文有2篇 |
| 96 | +counter[5] = 2 |
| 97 | +``` |
| 98 | + |
| 99 | +- 最后我们可以从后向前遍历数组 counter,因为要找最大的H值,所以这个时候要倒序从n到0遍历遍历,找出从后往前遍历时第一个满足引用次数>i的值,就是最大的h值。 |
| 100 | + |
| 101 | +- 注意要用累加,因为引用次数为5的2篇,一定也能满足引用次数>=3的条件 |
| 102 | + |
| 103 | +```java |
| 104 | +public class Solution { |
| 105 | + public int hIndex(int[] citations) { |
| 106 | + int n = citations.length, tot = 0; |
| 107 | + int[] counter = new int[n + 1]; |
| 108 | + for (int i = 0; i < n; i++) { |
| 109 | + if (citations[i] >= n) { |
| 110 | + counter[n]++; |
| 111 | + } else { |
| 112 | + counter[citations[i]]++; |
| 113 | + } |
| 114 | + } |
| 115 | + for (int i = n; i >= 0; i--) { |
| 116 | + tot += counter[i]; |
| 117 | + if (tot >= i) { |
| 118 | + return i; |
| 119 | + } |
| 120 | + } |
| 121 | + return 0; |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +复杂度分析 |
| 127 | + |
| 128 | +时间复杂度:O(n),其中 n 为数组 citations 的长度。需要遍历数组 citations 一次,以及遍历长度为 n+1 的数组 counter 一次。 |
| 129 | + |
| 130 | +空间复杂度:O(n),其中 n 为数组 citations 的长度。需要创建长度为 n+1 的数组 counter。 |
| 131 | + |
| 132 | + |
| 133 | +##### 方法三:二分法 |
| 134 | + |
| 135 | +所谓的 h 指数是指一个具体的数值,该数值为“最大”的满足「至少发表了 x 篇论文,且每篇论文至少被引用 x 次」定义的合法数,重点是“最大”。 |
| 136 | + |
| 137 | +给定所有论文的引用次数情况为[3,0,6,1,5],可统计满足定义的数值有哪些: |
| 138 | + |
| 139 | +``` |
| 140 | +h=0,含义为「至少发表了 0 篇,且这 0 篇论文至少被引用 0 次」,空集即满足,恒成立; |
| 141 | +
|
| 142 | +h=1,含义为「至少发表了 1 篇,且这 1 篇论文至少被引用 1 次」,可以找到这样的组合,如 [3],成立; |
| 143 | +
|
| 144 | +h=2,含义为「至少发表了 2 篇,且这 2 篇论文至少被引用 2 次」,可以找到这样的组合,如 [3, 6],成立; |
| 145 | +
|
| 146 | +h=3,含义为「至少发表了 3 篇,且这 3 篇论文至少被引用 3 次」,可以找到这样的组合,如 [3, 6, 5],成立; |
| 147 | +
|
| 148 | +h=4,含义为「至少发表了 4 篇,且这 4 篇论文至少被引用 4 次」,找不到这样的组合,不成立; |
| 149 | +
|
| 150 | +h=5,含义为「至少发表了 5 篇,且这 5 篇论文至少被引用 5 次」,找不到这样的组合,不成立; |
| 151 | +``` |
| 152 | +实际上,当遇到第一个无法满足的数时,更大的数值就没必要找了。 |
| 153 | + |
| 154 | + |
| 155 | +基于此分析,我们发现对于任意的 citations数组(论文总数量为该数组长度 n),都必然对应了一个最大的 h 值,且小于等于该 h 值的情况均满足,大于该 h 值的均不满足。 |
| 156 | + |
| 157 | +那么,在以最大 h 值为分割点的数轴上具有「二段性」,可通过「二分」求解该分割点(答案)。 |
| 158 | + |
| 159 | +最后考虑在什么值域范围内进行二分? |
| 160 | + |
| 161 | +一个合格的二分范围,仅需确保答案在此范围内即可。 |
| 162 | + |
| 163 | +再回看我们关于 h 的定义「至少发表了 x 篇论文,且每篇论文至少被引用 x 次」 |
| 164 | +综上,我们只需要在 [0,n] 范围进行二分即可。 |
| 165 | + |
| 166 | + |
| 167 | +设查找范围的初始左边界 left 为 0,初始右边界 right 为 n。每次在查找范围内取中点 mid,同时扫描整个数组,判断是否至少有 mid 个数大于 mid。如果有,说明要寻找的 h 在搜索区间的右边,反之则在左边。 |
| 168 | + |
| 169 | +二分的本质:前半部分符合要求、后半部分不符合要求,找出符合要求的最大索引 |
| 170 | + |
| 171 | + |
| 172 | +```java |
| 173 | +class Solution { |
| 174 | + public int hIndex(int[] cs) { |
| 175 | + int n = cs.length; |
| 176 | + int l = 0, r = n; |
| 177 | + while (l < r) { |
| 178 | + // 加1 是为了防止死循环 |
| 179 | + int mid = (l + r + 1) >> 1; |
| 180 | + if (check(cs, mid)) l = mid; |
| 181 | + else r = mid - 1; |
| 182 | + } |
| 183 | + return r; |
| 184 | + } |
| 185 | + // 判断是否存在至少mid篇论文的引用次数至少为mid。 |
| 186 | + boolean check(int[] cs, int mid) { |
| 187 | + int ans = 0; |
| 188 | + for (int i : cs) if (i >= mid) ans++; |
| 189 | + return ans >= mid; |
| 190 | + } |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | + |
| 195 | + |
| 196 | +###### 上面为什么要加1 |
| 197 | + |
| 198 | +- 这是因为整数除法的向下取整特性。 |
| 199 | +- 在计算mid时,如果使用(l+r)/2,当l和r相邻时(例如l=2, r=3),则mid=(2+3)/2=2(整数除法向下取整)。 |
| 200 | +- 如果此时check(mid)为真,那么我们会执行l=mid,即l=2,然后循环继续,再次计算mid=(2+3)/2=2,这样就进入了死循环。 |
| 201 | + |
| 202 | +为了避免这种死循环,我们使用(l + r + 1) / 2,使得当l和r相邻时,mid会等于r(因为(2+3+1)/2=6/2=3),然后无论走哪个分支,循环都会结束(因为执行l=3后l等于r,或执行r=mid-1=2后l等于r)。 |
| 203 | +所以,加1是为了避免在只剩两个数时陷入死循环,因为我们要找的是右边界(最大值)。 |
| 204 | + |
| 205 | + |
| 206 | + |
| 207 | +###### 例子: |
| 208 | + |
| 209 | +- citations = [3,0,6,1,5],用上述二分法: |
| 210 | +- 初始化:l=0, r=5 |
| 211 | +- mid = (0+5+1)/2 = 3,检查check(3): 统计>=3的个数,为3(3,5,6)-> 满足,所以l=3 |
| 212 | +- 接下来:l=3, r=5,mid=(3+5+1)/2=9/2=4(整数除法取整为4),检查check(4):统计>=4的个数,有2篇(5,6)不满足4(因为需要至少4篇) |
| 213 | +- 所以r=3循环结束,返回3。 |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | + |
| 218 | +复杂度分析 |
| 219 | + |
| 220 | +时间复杂度:O(nlogn),其中 n 为数组 citations 的长度。需要进行 logn 次二分搜索,每次二分搜索需要遍历数组 citations 一次。 |
| 221 | +空间复杂度:O(1),只需要常数个变量来进行二分搜索。 |
| 222 | + |
| 223 | + |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +--- |
| 228 | +- 邮箱 :charon.chui@gmail.com |
| 229 | +- Good Luck! |
| 230 | + |
| 231 | + |
0 commit comments