一、题目
给定一个无序的数组 nums,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0 。
您必须编写一个在「线性时间」内运行并使用「线性额外空间」的算法。
示例 1:
输入: nums = [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
示例 2:
输入: nums = [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 109
二、代码
class Solution {
public int maximumGap(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
int max = nums[0];
int min = nums[0];
// 求出数组中的最大值和最小值
for (int i = 1; i < n; i++) {
max = Math.max(max, nums[i]);
min = Math.min(min, nums[i]);
}
// 如果数组中的最大值和最小值是同一个数,说明整个数组是同一个值,最大差值为0
if (max == min) {
return 0;
}
// 每个桶中存三个数据,当前桶是否有值,桶中的最大值,桶中的最小值
// 创建n + 1个桶,利用鸽笼原理来求出最大差值
boolean[] hasNum = new boolean[n + 1];
int[] maxs = new int[n + 1];
int[] mins = new int[n + 1];
// 遍历nums数组,将所有的数添加到桶中
for (int i = 0; i < n; i++) {
// 计算当前的数应该放到哪个桶中
int index = bucket(n, max, min, nums[i]);
// 记录每个桶中的最大值和最小值
maxs[index] = hasNum[index] == true ? Math.max(maxs[index], nums[i]) : nums[i];
mins[index] = hasNum[index] == true ? Math.min(mins[index], nums[i]) : nums[i];
hasNum[index] = true;
}
// 注意,题目要求是相邻数的最大差值,只有前面桶的最大值和后面最近的有数的桶的最小值才是相邻的
// 左边离当前位置最近的非空桶的下标,用来找到距离自己左边最近的非空桶的最大值
int leftMaxIndex = -1;
// 最大差值
int ans = -1;
// 遍历所有的桶,找最大差值
for (int i = 0; i < n + 1; i++) {
// 最大差值一定是相邻的两个非空头的最大值和最小值的差值(两个非空桶可能挨着,也可能中间隔了一个空桶)
if (hasNum[i] == true) {
if (leftMaxIndex != -1) {
ans = Math.max(ans, mins[i] - maxs[leftMaxIndex]);
}
// 更新最近的非空桶
leftMaxIndex = i;
}
}
return ans;
}
// 计算当前数应该放到哪个桶里
// 使用long类型是为了避免在计算过程中产生溢出
public int bucket(long len, long max, long min, long num) {
return (int) ((num - min) * len / (max - min));
}
}
三、解题思路
n个数构造n + 1个桶,中间必存在空桶,空桶的左侧它一定有非空桶,空桶的右侧它一定也有非空桶,空桶右侧离它最近非空桶的最小值和它左侧离它最近非空桶的最大值必然相邻,而且这个差值是大于空桶的范围的。这样我们就排除了一种可能性,即来自一个桶内部的相邻数一律可以不需要考虑。最大差值不一定来自于空桶两侧离它右边最近的桶的最小值和离它左边最近桶的最大值,也有可能存在在相邻的两个桶中。所以我们就找相邻的非空桶的最大差值就是答案(相邻的非空桶可能中间隔了空桶,也可能是相邻的两个通)。