题目描述

✅ 164. 最大间距

image-20260928220030467

题意分析

将数组按升序排列后,求相邻两个元素之间的最大差值。相邻指排序之后的位置关系,不是原数组的相邻关系,也不是直接求最大值减最小值。

元素不足两个时返回零,重复值之间的间隔为零。题目要求线性时间、线性额外空间,因此不能依赖比较排序再扫描;只需找到最大间隔,不必实际得到完整排序结果。

解法:桶划分维护相邻非空桶

核心思路

[!blue]

设全局最小、最大值分别为 minValue、maxValue,跨度为 range。排序后共有 n - 1 个相邻间隔,它们的和恰好等于跨度,因此最大间隔至少达到平均值 range / (n - 1);间隔是整数,所以也不小于这个平均值的向上取整。

按数值范围划桶,桶宽取 bucketSize = max(1, range / (n - 1)),这里除法向下取整。两个数落在同一个桶中,它们的整数差最多为 bucketSize - 1;桶宽又不超过上述最大间隔的下界,因此真正的最大间隔不可能出现在同一桶内部。

只需保留每个非空桶的最小值和最大值,无需存放或排序桶内所有元素。桶编号随数值递增,按编号扫描时,当前非空桶的最小值与前一非空桶的最大值,恰好是一对跨桶相邻元素;中间的空桶没有元素,不会提供额外邻居。

桶下标是 (num - minValue) / bucketSize,桶数为 range / bucketSize + 1,最后的加一用于容纳最大值所在的桶。另用 used 标记是否有元素,不能让默认的零值参与最小值比较,因为零本身也是合法输入。

先处理不足两个元素或全部相等的情况,之后跨度为正、桶宽至少为一。扫描非空桶并比较两侧边界,就能覆盖全部可能产生最大差值的位置。

解题步骤

  1. 元素不足两个时返回零;否则扫描数组求全局最小、最大值,两者相等也返回零。
  2. 计算桶宽和覆盖整个值域所需的桶数,创建桶最小值、最大值及使用标记数组。
  3. 将每个数映射到对应桶。首次放入时同时设置该桶最小、最大值,后续仅更新两个边界。
  4. 从小到大扫描桶,空桶直接跳过;比较当前非空桶最小值与 previousMax,更新最大间隔。
  5. 将 previousMax 更新为当前桶最大值。它初始取全局最小值,使第一个非空桶只贡献零,不需要额外特判。

代码实现

class Solution {
    public int maximumGap(int[] nums) {
        int n = nums.length;

        if (n < 2) {
            return 0;
        }

        int minValue = nums[0];
        int maxValue = nums[0];

        for (int num : nums) {
            minValue = Math.min(minValue, num);
            maxValue = Math.max(maxValue, num);
        }

        if (minValue == maxValue) {
            return 0;
        }

        int range = maxValue - minValue;
        // 桶内整数差小于桶宽,不会产生需要寻找的最大间隔。
        int bucketSize = Math.max(1, range / (n - 1));
        int bucketCount = range / bucketSize + 1;
        int[] bucketMin = new int[bucketCount];
        int[] bucketMax = new int[bucketCount];
        boolean[] used = new boolean[bucketCount];

        for (int num : nums) {
            int index = (num - minValue) / bucketSize;

            if (!used[index]) {
                bucketMin[index] = bucketMax[index] = num;
                used[index] = true;
            } else {
                bucketMin[index] = Math.min(bucketMin[index], num);
                bucketMax[index] = Math.max(bucketMax[index], num);
            }
        }

        int answer = 0;
        int previousMax = minValue;

        for (int i = 0; i < bucketCount; i++) {
            if (!used[i]) {
                continue;
            }

            // 只比较相邻非空桶,空桶不改变前一个有效最大值。
            answer = Math.max(answer, bucketMin[i] - previousMax);
            previousMax = bucketMax[i];
        }

        return answer;
    }
}
func maximumGap(nums []int) int {
    n := len(nums)
    if n < 2 {
        return 0
    }

    minValue, maxValue := nums[0], nums[0]
    for _, num := range nums {
        if num < minValue {
            minValue = num
        }
        if num > maxValue {
            maxValue = num
        }
    }
    if minValue == maxValue {
        return 0
    }

    valueRange := maxValue - minValue
    // 桶内整数差小于桶宽,不会产生需要寻找的最大间隔。
    bucketSize := valueRange / (n - 1)
    if bucketSize < 1 {
        bucketSize = 1
    }
    bucketCount := valueRange/bucketSize + 1
    bucketMin := make([]int, bucketCount)
    bucketMax := make([]int, bucketCount)
    used := make([]bool, bucketCount)

    for _, num := range nums {
        index := (num - minValue) / bucketSize
        if !used[index] {
            bucketMin[index], bucketMax[index] = num, num
            used[index] = true
        } else {
            if num < bucketMin[index] {
                bucketMin[index] = num
            }
            if num > bucketMax[index] {
                bucketMax[index] = num
            }
        }
    }

    answer, previousMax := 0, minValue
    for i := 0; i < bucketCount; i++ {
        if !used[i] {
            continue
        }
        // 只比较相邻非空桶,空桶不改变前一个有效最大值。
        if gap := bucketMin[i] - previousMax; gap > answer {
            answer = gap
        }
        previousMax = bucketMax[i]
    }
    return answer
}

复杂度分析

  • 时间复杂度:$O(n)$,求最值、分桶、扫描桶各一遍,桶数为 $O(n)$。
  • 空间复杂度:$O(n)$。若平均间隔小于一,桶宽为一,桶数不超过 n;否则向下取整后的桶宽大于平均间隔的一半,桶数小于 2n。每桶只保存两个边界和一个使用标记。

关键点总结

[!green]

  • 相邻间隔总和固定,平均值提供最大间隔的下界。
  • 桶内最大差严格小于这一候选下界,才有依据舍弃桶内排序。
  • 按值域有序排列的桶只需比较相邻非空桶的“前最大、后最小”。
  • 桶宽既排除了桶内答案,也限制桶数为线性规模,这两点共同保证算法成立。

易错点总结

[!yellow]

  • 直接计算全局最大值减最小值,得到的是整段跨度,不是排序后相邻元素的差。
  • 没有提前处理 n < 2,平均间隔的分母可能为零;桶宽也必须至少为一。
  • 桶数漏加一,最大元素对应的桶下标可能超出数组范围。
  • 默认用零初始化桶最小值却不区分空桶,正数桶可能错误保留一个不存在的零。
  • 扫描到空桶仍更新前一桶信息,破坏相邻非空桶的实际边界。
  • 用两个桶的最小值相减,或两个最大值相减,都不能表示排序后的跨桶相邻间隔。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/43611748
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!