一、题目描述
给定一个无序的数组 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 <= 10^5
0 <= nums[i] <= 10^9
二、解题思路
-
确定桶的大小:首先找出数组中的最大值和最小值,计算出差值。这个差值将用于确定桶的大小。每个桶的容量是
(最大值 - 最小值) / (数组长度 - 1)
。 -
创建桶:根据上述计算出的桶大小,创建足够的桶。每个桶只存储该区间内的最大值和最小值。
-
分配数字到桶中:遍历数组,将每个数字分配到相应的桶中。对于每个桶,只需要记录该桶内的最大值和最小值,因为最大差值只可能出现在相邻桶之间。
-
找出最大差值:遍历桶,计算相邻非空桶之间的最小值之差,最大的那个差值就是答案。
三、具体代码
class Solution {public int maximumGap(int[] nums) {if (nums == null || nums.length < 2) {return 0;}int min = nums[0];int max = nums[0];for (int num : nums) {min = Math.min(min, num);max = Math.max(max, num);}int bucketSize = Math.max(1, (max - min) / (nums.length - 1));int bucketCount = (max - min) / bucketSize + 1;int[] bucketMin = new int[bucketCount];int[] bucketMax = new int[bucketCount];Arrays.fill(bucketMin, Integer.MAX_VALUE);Arrays.fill(bucketMax, Integer.MIN_VALUE);for (int num : nums) {int index = (num - min) / bucketSize;bucketMin[index] = Math.min(bucketMin[index], num);bucketMax[index] = Math.max(bucketMax[index], num);}int maxGap = 0;int previousMax = bucketMax[0];for (int i = 1; i < bucketCount; i++) {if (bucketMin[i] != Integer.MAX_VALUE) {maxGap = Math.max(maxGap, bucketMin[i] - previousMax);previousMax = bucketMax[i];}}return maxGap;}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
-
遍历数组以找到最大值和最小值:这一步的时间复杂度是O(n),其中n是数组的长度,因为我们需要遍历整个数组一次。
-
初始化桶:初始化桶的时间复杂度是O(1),因为桶的数量与数组的长度成线性关系,但是这个操作是常数时间的。
-
分配数字到桶中:这一步的时间复杂度也是O(n),因为我们再次遍历数组,并将每个元素放入相应的桶中。每个操作是常数时间的。
-
找出最大差值:这一步的时间复杂度是O(m),其中m是桶的数量。在最坏的情况下,m可以接近n,但是通常情况下,m会远小于n,因为桶的大小是由数组的长度决定的。
综上所述,整个算法的时间复杂度是O(n),因为所有的步骤都是线性的,没有步骤会超过O(n)的时间复杂度。
2. 空间复杂度
-
桶数组:我们需要两个数组来存储每个桶的最小值和最大值,这两个数组的长度与桶的数量相同。在最坏的情况下,桶的数量可以接近数组的长度n,因此空间复杂度是O(n)。
-
其他变量:除了桶数组之外,我们只使用了几个额外的变量来存储最小值、最大值、桶大小等,这些变量的空间占用是常数的,因此可以忽略不计。
综合以上分析,整个算法的空间复杂度是O(n),因为我们需要存储每个桶的最小值和最大值,这需要与数组长度成线性关系的额外空间。
五、总结知识点
-
线性时间复杂度算法设计:代码的核心在于寻找一个线性时间复杂度的算法来解决问题,这是通过对数组进行一次遍历来实现的。
-
桶排序(Bucket Sort):虽然代码并不是为了对数组进行完全排序,但是它使用了桶排序的思想。桶排序是一种将元素分配到有限数量的桶中,然后对每个桶进行单独排序的算法。在这个问题中,我们只关心每个桶的最大值和最小值,因为最大差值只能出现在相邻桶之间。
-
空间换时间:通过使用额外的空间(桶数组)来减少时间复杂度。这是一种常见的编程技巧,在不能牺牲时间复杂度的场合特别有用。
-
数组操作:代码中使用了数组的填充(
Arrays.fill
)和遍历等基本操作。 -
数学计算:计算桶的大小和数量涉及到基本的数学运算,如除法和加法。
-
边界条件处理:代码首先检查输入数组的长度,如果长度小于2,则直接返回0,这是因为长度小于2的数组不存在相邻元素。
-
最小值和最大值的初始化:在遍历数组之前,将最小值和最大值初始化为数组的第一个元素,这是一种常见的初始化技巧。
-
遍历和比较:代码中多次使用遍历和比较操作来找到最小值、最大值以及最大差值。
-
整数除法:在计算桶的大小时,使用了整数除法,这会向下取整,确保每个桶的大小合适。
-
无穷大和无穷小值的处理:使用
Integer.MAX_VALUE
和Integer.MIN_VALUE
来初始化桶的最小值和最大值,这是一种常见的做法,以便在后续的比较中正确地识别出桶中的实际最小值和最大值。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。