Skip to content

Commit 0e3f60b

Browse files
committed
add algorithm part
1 parent 428cb4e commit 0e3f60b

25 files changed

Lines changed: 1992 additions & 11 deletions
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
1.合并两个有序数组
2+
===
3+
4+
5+
### 题目
6+
7+
给你两个按`非递减顺序`排列的整数数组`nums1``nums2`,另有两个整数`m``n`,分别表示`nums1``nums2`中的元素数目。
8+
9+
请你`合并`nums2到nums1中,使合并后的数组同样按非递减顺序排列。
10+
11+
注意:最终,合并后数组不应由函数返回,而是存储在数组nums1中。为了应对这种情况,nums1的初始长度为`m + n`,其中前m个元素表示应合并的元素,后n个元素为0,应忽略。nums2的长度为n。
12+
13+
14+
15+
示例 1:
16+
17+
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
18+
输出:[1,2,2,3,5,6]
19+
解释:需要合并 [1,2,3][2,5,6]
20+
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
21+
22+
示例 2:
23+
24+
输入:nums1 = [1], m = 1, nums2 = [], n = 0
25+
输出:[1]
26+
解释:需要合并 [1][]
27+
合并结果是 [1]
28+
29+
示例 3:
30+
31+
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
32+
输出:[1]
33+
解释:需要合并的数组是 [][1]
34+
合并结果是 [1]
35+
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
36+
37+
38+
39+
### 思路
40+
41+
逆向双指针:
42+
- nums1中是非递减的数组。
43+
- nums2中也是非递减数组。
44+
- 所以我们要做的就是把nums1中前m个元素与nums2中的元素进行倒序比较,将大的值倒序放到nums1中的后面。
45+
- 因为nums1中的数据已经是非递减的了,所以等nums2中的内容都放置完就可以结束。
46+
47+
```python
48+
class Solution:
49+
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
50+
index = m + n - 1
51+
p1 = m - 1
52+
p2 = n - 1
53+
while p2 >= 0:
54+
if p1 >= 0 and nums1[p1] >= nums2[p2]:
55+
nums1[index] = nums1[p1]
56+
index += 1
57+
p1 -= 1
58+
else:
59+
nums1[index] = nums2[p2]
60+
index += 1
61+
p2 -= 1
62+
```
63+
64+
```kotlin
65+
class Solution {
66+
fun merge(nums1: IntArray, m: Int, nums2: IntArray, n: Int): Unit {
67+
var index = nums1.lastIndex
68+
var r1 = m - 1
69+
var r2 = n - 1
70+
while(r2 >= 0) {
71+
if (r1 >= 0 && nums1[r1] >= nums2[r2]) {
72+
nums1[index --] = nums1[r1--]
73+
} else {
74+
nums1[index --] = nums2[r2 --]
75+
}
76+
}
77+
}
78+
}
79+
```
80+
81+
---
82+
- 邮箱 :charon.chui@gmail.com
83+
- Good Luck!
84+
85+

Algorithm/10.跳跃游戏II.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
10.跳跃游戏II
2+
===
3+
4+
5+
### 题目
6+
7+
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]
8+
9+
每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
10+
11+
0 <= j <= nums[i]
12+
i + j < n
13+
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
14+
15+
16+
17+
示例 1:
18+
19+
输入: nums = [2,3,1,1,4]
20+
输出: 2
21+
解释: 跳到最后一个位置的最小跳跃数是 2。
22+
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
23+
示例 2:
24+
25+
输入: nums = [2,3,0,1,4]
26+
输出: 2
27+
28+
29+
### 思路
30+
31+
32+
贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最少步数。
33+
34+
所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
35+
36+
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
37+
38+
- 如下图,开始的位置是 2,可跳的范围是橙色的。然后因为 3 可以跳的更远,所以跳到 3 的位置。
39+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/jump_2_1.png?raw=true)
40+
- 然后现在的位置就是 3 了,能跳的范围是橙色的,然后因为 4 可以跳的更远,所以下次跳到 4 的位置。
41+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/jump_2_2.png?raw=true)
42+
43+
44+
```python
45+
46+
class Solution:
47+
def jump(self, nums: List[int]) -> int:
48+
end = 0
49+
maxPosition = 0
50+
steps = 0
51+
for i in range(len(nums) - 1):
52+
maxPosition = max(maxPosition, i + nums[i])
53+
if i == end:
54+
steps += 1
55+
end = maxPosition
56+
57+
return steps
58+
```
59+
60+
---
61+
- 邮箱 :charon.chui@gmail.com
62+
- Good Luck!
63+
64+

Algorithm/11.H指数.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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

Comments
 (0)