Skip to content

Commit 37678a3

Browse files
committed
164
1 parent d3b604d commit 37678a3

File tree

3 files changed

+256
-3
lines changed

3 files changed

+256
-3
lines changed

SUMMARY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
* [98. Validate Binary Search Tree](leetCode-98-Validate-Binary-Search-Tree.md)
103103
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
104104
* [100. Same Tree](leetcode-100-Same-Tree.md)
105-
* [101 题到 162题](leetcode-101-200.md)
105+
* [101 题到 164题](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
@@ -159,4 +159,5 @@
159159
* [154*. Find Minimum in Rotated Sorted Array II](leetcode-154-Find-Minimum-in-Rotated-Sorted-ArrayII.md)
160160
* [155. Min Stack](leetcode-155-Min-Stack.md)
161161
* [160. Intersection of Two Linked Lists](leetcode-160-Intersection-of-Two-Linked-Lists.md)
162-
* [162. Find Peak Element](leetcode-162-Find-Peak-Element.md)
162+
* [162. Find Peak Element](leetcode-162-Find-Peak-Element.md)
163+
* [164. Maximum Gap](leetcode-164-Maximum-Gap.md)

leetcode-101-200.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,6 @@
110110

111111
<a href="leetcode-160-Intersection-of-Two-Linked-Lists.html">160. Intersection of Two Linked Lists</a>
112112

113-
<a href="leetcode-162-Find-Peak-Element.html">162. Find Peak Element</a>
113+
<a href="leetcode-162-Find-Peak-Element.html">162. Find Peak Element</a>
114+
115+
<a href="leetcode-164-Maximum-Gap.html">164. Maximum Gap</a>

leetcode-164-Maximum-Gap.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# 题目描述(困难难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/164.jpg)
4+
5+
给一个乱序的数组,求出数组排序以后的相邻数字的差最大是多少。
6+
7+
# 解法一
8+
9+
先来个直接的,就按题目的意思,先排序,再搜索。
10+
11+
```java
12+
public int maximumGap(int[] nums) {
13+
Arrays.sort(nums);
14+
int max = 0;
15+
for (int i = 0; i < nums.length - 1; i++) {
16+
if (nums[i + 1] - nums[i] > max) {
17+
max = nums[i + 1] - nums[i];
18+
}
19+
}
20+
return max;
21+
}
22+
```
23+
24+
但是正常的排序算法时间复杂度会是 `O(nlog(n))`,题目要求我们在 `O(n)` 的复杂度下去求解。
25+
26+
自己想了各种思路,但想不出怎么降到 `O(n)`,把 [官方](https://leetcode.com/problems/maximum-gap/solution/) 给的题解分享一下吧。
27+
28+
我们一般排序算法多用快速排序,平均时间复杂度是 `O(nlog(n))`,其实还有一种排序算法,时间复杂度是 `O(kn)``k` 是最大数字的位数,当 `k` 远小于 `n` 的时候,时间复杂度可以近似看成 `O(n)`。这种排序算法就是基数排序,下边讲一下具体思想。
29+
30+
比如这样一个数列排序: `342 58 576 356`, 我们来看一下怎么排序:
31+
32+
```java
33+
不足的位数看做是 0
34+
342 058 576 356
35+
按照个位将数字依次放到不同的位置
36+
0:
37+
1:
38+
2: 342
39+
3:
40+
4:
41+
5:
42+
6: 576, 356
43+
7:
44+
8: 058
45+
9:
46+
47+
把上边的数字依次拿出来,组成新的序列 342 576 356 058,然后按十位继续放到不同的位置。
48+
0:
49+
1:
50+
2:
51+
3:
52+
4: 342
53+
5: 356 058
54+
6:
55+
7: 576
56+
8:
57+
9:
58+
59+
把上边的数字依次拿出来,组成新的序列 342 356 058 576,然后按百位继续装到不同的位置。
60+
0: 058
61+
1:
62+
2:
63+
3: 342 356
64+
4:
65+
5: 576
66+
6:
67+
7:
68+
8:
69+
9:
70+
71+
把数字依次拿出来,最终结果就是 58 342 356 576
72+
```
73+
74+
为了代码更好理解, 我们可以直接用 `10``list` 去存放每一组的数字,官方题解是直接用一维数组实现的。
75+
76+
对于取各个位的的数字,我们通过对数字除以 `1`, `10`, `100`... 然后再对 `10` 取余来实现。
77+
78+
```java
79+
public int maximumGap(int[] nums) {
80+
if (nums.length <= 1) {
81+
return 0;
82+
}
83+
List<ArrayList<Integer>> lists = new ArrayList<>();
84+
for (int i = 0; i < 10; i++) {
85+
lists.add(new ArrayList<>());
86+
}
87+
int n = nums.length;
88+
int max = nums[0];
89+
//找出最大的数字
90+
for (int i = 1; i < n; i++) {
91+
if (max < nums[i]) {
92+
max = nums[i];
93+
}
94+
}
95+
int m = max;
96+
int exp = 1;
97+
//一位一位的进行
98+
while (max > 0) {
99+
//将之前的元素清空
100+
for (int i = 0; i < 10; i++) {
101+
lists.set(i, new ArrayList<>());
102+
}
103+
//将数字放入对应的位置
104+
for (int i = 0; i < n; i++) {
105+
lists.get(nums[i] / exp % 10).add(nums[i]);
106+
}
107+
108+
//将数字依次拿出来
109+
int index = 0;
110+
for (int i = 0; i < 10; i++) {
111+
for (int j = 0; j < lists.get(i).size(); j++) {
112+
nums[index] = lists.get(i).get(j);
113+
index++;
114+
}
115+
116+
}
117+
max /= 10;
118+
exp *= 10;
119+
}
120+
121+
int maxGap = 0;
122+
for (int i = 0; i < nums.length - 1; i++) {
123+
if (nums[i + 1] - nums[i] > maxGap) {
124+
maxGap = nums[i + 1] - nums[i];
125+
}
126+
}
127+
return maxGap;
128+
}
129+
```
130+
131+
# 解法二
132+
133+
上边的解法还不是真正的 `O(n)`,下边继续介绍另一种解法,参考了 [这里](https://leetcode.com/problems/maximum-gap/discuss/50643/bucket-sort-JAVA-solution-with-explanation-O(N)-time-and-space) ,评论区对自己帮助很多。
134+
135+
我们知道如果是有序数组的话,我们就可以通过计算两两数字之间差即可解决问题。
136+
137+
那么如果是更宏观上的有序呢?
138+
139+
```java
140+
我们把 0 3 4 6 23 28 29 33 38 依次装到三个箱子中
141+
0 1 2 3
142+
------- ------- ------- -------
143+
| 3 4 | | | | 29 | | 33 |
144+
| 6 | | | | 23 | | |
145+
| 0 | | | | 28 | | 38 |
146+
------- ------- ------- -------
147+
0 - 9 10 - 19 20 - 29 30 - 39
148+
我们把每个箱子的最大值和最小值表示出来
149+
min max min max min max min max
150+
0 6 - - 23 29 33 38
151+
```
152+
153+
我们可以只计算相邻箱子 `min``max` 的差值来解决问题吗?空箱子直接跳过。
154+
155+
`2` 个箱子的 `min` 减第 `0` 个箱子的 `max``23 - 6 = 17`
156+
157+
`3` 个箱子的 `min` 减第 `2` 个箱子的 `max``33 - 29 = 4`
158+
159+
看起来没什么问题,但这样做一定需要一个前提,因为我们只计算了相邻箱子的差值,没有计算箱子内数字的情况,所以我们需要保证每个箱子里边的数字一定不会产生最大 `gap`
160+
161+
我们把箱子能放的的数字个数记为 `interval`,给定的数字中最小的是 `min`,最大的是 `max`。那么箱子划分的范围就是 `min ~ (min + 1 * interval - 1)``(min + 1 * interval) ~ (min + 2 * interval - 1)``(min + 2 * interval) ~ (min + 3 * interval - 1)`...,上边举的例子中, `interval` 我们其实取了 `10`
162+
163+
划定了箱子范围后,我们其实很容易把数字放到箱子中,通过 `(nums[i] - min) / interval` 即可得到当前数字应该放到的箱子编号。那么最主要的问题其实就是怎么去确定 `interval`
164+
165+
`interval` 过小的话,需要更多的箱子去存储,很费空间,此外箱子增多了,比较的次数也会增多,不划算。
166+
167+
`interval` 过大的话,箱子内部的数字可能产生题目要求的最大 `gap`,所以肯定不行。
168+
169+
所以我们要找到那个保证箱子内部的数字不会产生最大 `gap`,并且尽量大的 `interval`
170+
171+
继续看上边的例子,`0 3 4 6 23 28 29 33 38`,数组中的最小值 `0` 和最大值 `38` ,并没有参与到 `interval` 的计算中,所以它俩可以不放到箱子中,还剩下 `n - 2` 个数字。
172+
173+
像上边的例子,如果我们保证至少有一个空箱子,那么我们就可以断言,箱子内部一定不会产生最大 `gap`
174+
175+
因为在我们的某次计算中,会跳过一个空箱子,那么得到的 `gap` 一定会大于 `interval`,而箱子中的数字最大的 `gap``interval - 1`
176+
177+
接下来的问题,怎么保证至少有一个空箱子呢?
178+
179+
鸽巢原理的变形,有 `n - 2` 个数字,如果箱子数多于 `n - 2` ,那么一定会出现空箱子。总范围是 `max - min`,那么 `interval = (max - min) / 箱子数`,为了使得 `interval` 尽量大,箱子数取最小即可,也就是 `n - 1`
180+
181+
所以 `interval = (max - min) / n - 1` 。这里如果除不尽的话,我们 `interval` 可以向上取整。因为我们给定的数字都是整数,这里向上取整的话对于最大 `gap` 是没有影响的。比如原来范围是 `[0,5.5)`,那么内部产生的最大 `gap``5 - 0 = 5`。现在向上取整,范围变成`[0,6)`,但是内部产生的最大 `gap` 依旧是 `5 - 0 = 5`
182+
183+
所有问题都解决了,可以安心写代码了。
184+
185+
```java
186+
public int maximumGap(int[] nums) {
187+
if (nums.length <= 1) {
188+
return 0;
189+
}
190+
int n = nums.length;
191+
int min = nums[0];
192+
int max = nums[0];
193+
//找出最大值、最小值
194+
for (int i = 1; i < n; i++) {
195+
min = Math.min(nums[i], min);
196+
max = Math.max(nums[i], max);
197+
}
198+
if(max - min == 0) {
199+
return 0;
200+
}
201+
202+
//算出每个箱子的范围
203+
int interval = (int) Math.ceil((double)(max - min) / (n - 1));
204+
205+
//每个箱子里数字的最小值和最大值
206+
int[] bucketMin = new int[n - 1];
207+
int[] bucketMax = new int[n - 1];
208+
209+
//最小值初始为 Integer.MAX_VALUE
210+
Arrays.fill(bucketMin, Integer.MAX_VALUE);
211+
//最小值初始化为 -1,因为题目告诉我们所有数字是非负数
212+
Arrays.fill(bucketMax, -1);
213+
214+
//考虑每个数字
215+
for (int i = 0; i < nums.length; i++) {
216+
//当前数字所在箱子编号
217+
int index = (nums[i] - min) / interval;
218+
//最大数和最小数不需要考虑
219+
if(nums[i] == min || nums[i] == max) {
220+
continue;
221+
}
222+
//更新当前数字所在箱子的最小值和最大值
223+
bucketMin[index] = Math.min(nums[i], bucketMin[index]);
224+
bucketMax[index] = Math.max(nums[i], bucketMax[index]);
225+
}
226+
227+
int maxGap = 0;
228+
//min 看做第 -1 个箱子的最大值
229+
int previousMax = min;
230+
//从第 0 个箱子开始计算
231+
for (int i = 0; i < n - 1; i++) {
232+
//最大值是 -1 说明箱子中没有数字,直接跳过
233+
if (bucketMax[i] == -1) {
234+
continue;
235+
}
236+
237+
//当前箱子的最小值减去前一个箱子的最大值
238+
maxGap = Math.max(bucketMin[i] - previousMax, maxGap);
239+
previousMax = bucketMax[i];
240+
}
241+
//最大值可能处于边界,不在箱子中,需要单独考虑
242+
maxGap = Math.max(max - previousMax, maxGap);
243+
return maxGap;
244+
245+
}
246+
```
247+
248+
#
249+
250+
这道题主要是对排序算法的了解,第一次见到了基数排序的应用,解法三其实是桶排序的步骤。

0 commit comments

Comments
 (0)