【经典专题】无序数组的最大间距(Max Gap)——线性的桶排序

Q:给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。
 (请尝试在线性时间复杂度和空间复杂度的条件下解决此问题)

input:6 12 2 4 3 17 11 5 10
 
output:5

 
 

思路分析

  • 先sort然后一次遍历,虽然简单直观但显然不满足O(n)
  • 排序如何才能降到O(n)呢?桶排序,用空间换时间!
  • 桶排序的两个经典问题:桶的宽度是多少?有多少个桶?似乎遇到了瓶颈。

 
 

深入理解

想象一下,长度为 ( m a x − m i n ) (max-min) (maxmin)的直线上有N个点,假设,取最极端的情况——所有点在直线上均匀分布,即相邻两点之间的距离为 [ ( m a x − m i n ) / ( N − 1 ) ] [(max - min) / (N - 1)] [(maxmin)/(N1)]

注意!这些均匀分布点,只要有一个点不老实发生了偏移,必然会导致出现一个比上面的距离更大的相邻间距(一定是一个距离减小,另一个增大)

再看看上面的数字,显然不是老老实实等距分布的。那么,我们所要寻找的「最大间距」,必然是大于理想的「均匀间距」的

我们就取「桶的宽度」为这个理想的均匀间距,并结合长度 ( m a x − m i n ) (max-min) (maxmin)就能轻易得到「桶的个数」。为什么这样取呢?

这样可以保证,最大间距一定出现在桶之间,而绝不会出现在桶内部 —— 这句话是本算法的灵魂

 
 

细节

  • 桶的宽度 d = ( m a x − m i n ) / ( N − 1 ) (max - min) / (N - 1) (maxmin)/(N1) —— 向下取整,这是为了保证最大间距是大于它的
  • 桶的个数 bucketNum = [ ( m a x − m i n ) / d ] + 1 [(max - min) / d] + 1 [(maxmin)/d]+1 —— 多取1,这是保证最大值也有桶可放
  • 桶的编号 index = ( n u m s [ i ] − m i n ) / d (nums[i] - min) / d (nums[i]min)/d
  • 如何计算桶之间的间距?这是通过维护每个桶中的最大/最小值实现的。

 
 

举个栗子

9个数字,已排序 —— 1, 3, 4, 5, 6, 10, 11, 12, 17

桶的宽度 d = 2

桶的个数 bucket = 9

[1, 3)[3, 5)[5, 7)[7, 9)[9, 11)[11, 13)[13, 15)[15, 17)[17, 19)
13, 45, 61011, 1217

最大间距 maxGap = 17 - 12 = 5

 
 

代码

class Solution {
    public int maximumGap(int[] nums) {
    	// 数组长度小于2直接 return 0
        int len = nums.length;
        if(len < 2){
            return 0;
        }

        int min = Arrays.stream(nums).min().getAsInt();
        int max = Arrays.stream(nums).max().getAsInt();
        int d = Math.max(1, (max - min) / (len - 1));   // 桶的宽度
        int bucketNum = (max - min) / d + 1;            // 桶的个数

        // 初始化桶
        int[][] bucket = new int[bucketNum][2];
        for(int[] arr : bucket){
            Arrays.fill(arr, -1);   // (-1, -1)表示空桶
        }

        // 向桶中装填数字,维护每个桶中的最大/最小值
        for(int i = 0; i < len; i++){
            int index = (nums[i] - min) / d;
            if(bucket[index][0] == -1){
                bucket[index][0] = nums[i];
                bucket[index][1] = nums[i];
            }else{
                bucket[index][0] = Math.min(bucket[index][0], nums[i]);
                bucket[index][1] = Math.max(bucket[index][1], nums[i]);
            }
        }

        // 遍历桶
        // 后一个桶的最小值 - 前一个桶的最大值,尝试更新res
        int res = 0;
        int pre = -1;
        for(int index = 0; index < bucket.length; index++){
            if(bucket[index][0] == -1){
                continue;
            }
            if(pre != -1){
                res = Math.max(res, bucket[index][0] - bucket[pre][1]);
            }
            pre = index;
        }
        return res;
    }
}

 
 

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  • 桶排序的O(n)…称不上严谨,但理论上来说的确是O(n)的数量级 >_<

 
 
 
 
 
 
 
 
 
 
 
 

E N D END END

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值