[LeetCode164. 最大间距] | 刷题打卡

334 阅读4分钟

一、题目描述:

给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。 如果数组元素个数小于 2,则返回 0。

示例 1:

输入: [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。

示例 2:

输入: [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。

说明:

  • 你可以假设数组中所有元素都是非负整数,且数值在 32 位有符号整数范围内。
  • 请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。

二、思路分析

本题肯定是需要排序的,但要求时间复杂度O(n),那就只能用桶排序。关于桶排序,参考这篇博客

所以基本的思路就是,我们遍历一遍数组,找到整个数组的最大值maxv和最小值minv。这样就确定了元素的范围,接下来只要确定每个桶所能容纳的元素范围len,我们就能求出桶的个数count。有了这些桶之后,我们遍历数组,根据nums[i]确定元素应该放在哪个桶中,然后按顺序遍历每个桶,就可以得到排序数组了。

如何求出len呢?最直接的作法是让len = 1。但是这样的话,我们的count是元素范围(maxv - minv + 1)。题中说明数值在32位整数范围内,证明我们最坏情况可能会开个桶,空间开销极大

因此,在桶排序的基础上,我们要进行优化

我们可以这么来优化:题目要求我们求的是排序后相邻元素的最大差值,因此我们的目标是找到所有的排序数组相邻差值,然后取Max。那么我们可以每个桶中的任意元素之差都要小于最大差值,那么最大差值就必然出现在相邻的桶中。(也就是后一个桶中的最小值 - 前一个桶的最大值)这样的话,我们在遍历的时候,就只需要关心这个桶中存放的最大值和最小值即可。

那么现在问题就变成了我们如何设置桶的大小,来保证最大差值不存在于桶中呢?我们就需要来进行数学推导了:

1、假设每个元素都是均匀排列的,如: 0 5 10 15 20 25 30 35 40,此时最大间距是5。假设每个元素不是均匀排列,那么最大间距必定大于5。因此我们只需要让桶内部的间距小于平均间距,那么最大间距必然出现在桶间(设桶内间距为interval)

2、前面我们搞定了桶内间距,现在我们来思考一共需要几个桶。我们总共需要放n - 2个元素,因此我们其实只需要n - 2个桶,有两种情况,一种是存在空桶,那么桶间距必然大于桶内距离。另一种是每个元素分别在一个桶中,这种情况,就相当于已经排序了的数组,那么从前往后计算差值即可。

一个小细节

interval = (maxv - minv) / (n - 2) 不是整数应该上取整还是下去整

我们这里认定为上取整,令每个桶的范围是:[0, interval),那么每个桶的范围是:

  1. minv + 0 * interval ~ minv + 1 * interval - 1;
  2. minv + 1 * interval ~ minv + 2 * interval - 1;
  3. minv + 2 * interval ~ minv + 3 * interval - 1;
  4. minv + k * interval ~ minv + (k+1) * interval - 1;

这样我们将一个元素(nums[i]) 映射到桶的编号时,就可以用 ID = (nums[i] - minv) / intervals >> 0;

AC代码

/**
 * @param {number[]} nums
 * @return {number}
 */

class Bucket {
    max = Number.MIN_SAFE_INTEGER
    min = Number.MAX_SAFE_INTEGER
    used = false
}

var maximumGap = function(nums) {
    const n = nums.length;
    if(n < 2) return 0; 
    let maxv = Math.max(...nums), minv = Math.min(...nums);
    if(maxv == minv) return 0;
    
    // 桶间间隔
    const intervals = Math.ceil((maxv - minv) / (n - 2));
    const buckets = Array.from({length: n - 1}, () => new Bucket());

    // 将元素放置到对应的桶中
    for(let i = 0; i < n; ++ i) {
        if(nums[i] == minv || nums[i] == maxv) continue;
        const ID = (nums[i] - minv) / intervals >> 0;
        buckets[ID].used = true;
        buckets[ID].max = Math.max(nums[i], buckets[ID].max);
        buckets[ID].min = Math.min(nums[i], buckets[ID].min);
    }

    let maxGap = 0, preMax = minv;
    for(let i = 0; i < n - 1; ++ i) 
        if(buckets[i].used) {
            maxGap = Math.max(maxGap, buckets[i].min - preMax);
            preMax = buckets[i].max;
        }
    maxGap = Math.max(maxGap, maxv - preMax);
    return maxGap;
};

总结

本题在LeetCode中难度算是较高的,主要是其思维即具跳跃性,解题思路也不适用于其他题目,所以如果不理解的话也不用懊恼,掌握基本的算法和数据结构并熟练使用才是重中之重。

本文正在参与「掘金 3 月闯关活动」,点击查看活动详情