LeetCode 164. 最大间距

2 阅读3分钟

leetcode.cn/problems/ma…

给定一个无序的数组 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

方法二:基于桶的算法

思路与算法

设长度为 N 的数组中最大值为 max,min,则不难发现相邻数字的最大间距不会小于 ⌈(max−min)/(N−1)⌉。

为了说明这一点,我们使用反证法:假设相邻数字的间距都小于 ⌈(max−min)/(N−1)⌉,并记数组排序后从小到大的数字依次为 A 1 ​ ,A 2 ​ ,...,A N ​ ,则有

A N ​ −A 1 ​

=(A N ​ −A N−1 ​ )+(A N−1 ​ −A N−2 ​ )+...+(A 2 ​ −A 1 ​ ) <⌈(max−min)/(N−1)⌉+⌈(max−min)/(N−1)⌉+...+⌈(max−min)/(N−1)⌉ <(N−1)⋅⌈(max−min)/(N−1)⌉=max−min ​

但根据 A 1 ​ ,A N ​ 的定义,一定有 A 1 ​ =min,且 A N ​ =max,故上式会导出矛盾。

因此,我们可以选取整数 d=⌊(max−min)/(N−1)⌋<⌈(max−min)/(N−1)⌉。随后,我们将整个区间划分为若干个大小为 d 的桶,并找出每个整数所在的桶。根据前面的结论,能够知道,元素之间的最大间距一定不会出现在某个桶的内部,而一定会出现在不同桶当中。

因此,在找出每个元素所在的桶之后,我们可以维护每个桶内元素的最大值与最小值。随后,只需从前到后不断比较相邻的桶,用后一个桶的最小值与前一个桶的最大值之差作为两个桶的间距,最终就能得到所求的答案。

代码如下:

class Solution {
    fun maximumGap(nums: IntArray): Int {
        val n = nums.size
        // 少于两个数不用比较
        if (n < 2) return 0

        val minVal = nums.min()
        val maxVal = nums.max()
        // 桶区间大小因子d
        val d = maxOf(1, (maxVal - minVal) / (n - 1))

        // 在目标数组中需要划分的 桶 的个数, +1是为了消除d取整前的小数部分带来的误差(注意运算顺序)
        val bucketSize = (maxVal - minVal) / d + 1

        // 创建二维数组,每个桶初始化为 [-1, -1]
        // 存储 (桶内最小值,桶内最大值) 对, (-1, -1) 表示该桶是空的
        val bucket = Array(bucketSize) { IntArray(2) { -1 } }

        for (num in nums) {
            // 通过此运算用来判断当前元素属于哪个桶区间
            val idx = (num - minVal) / d

            // 若区间为空,则此元素是第一次进来,则最大值和最小值都是它
            if (bucket[idx][0] == -1) {
                bucket[idx][0] = num
                bucket[idx][1] = num
            } else {
                // 若区间存在其他元素,则开始下面比较
                // 当前元素与此桶区间的最小值比较,若比最小值还小,就把最小值替换成当前元素值
                bucket[idx][0] = minOf(bucket[idx][0], num)
                // 当前元素与此桶区间的最大值比较,若比最大值还大,就把最大值替换成当前元素值
                bucket[idx][1] = maxOf(bucket[idx][1], num)
            }
        }

        // 目标值
        var result = 0
        // 上一个下标
        var preIndex = -1

        // 开始遍历每个桶列表
        for (i in 0..<bucketSize) {
            // 判断当前桶区间是否为空,空则跳出
            if (bucket[i][0] == -1) continue
            // 判断上一个下标是否为空,即是否第一次循环
            if (preIndex != -1) {
                /*
                * 计算上一个桶区间的最大值,与当前桶区间的的最小值的差
                * 然后与ret目标值比较,取最大值然后替换ret值
                */
                result = maxOf(result, bucket[i][0] - bucket[preIndex][1])
            }
            // 上一个下标自增
            preIndex = i
        }
        return result
    }
}