题目:
给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。
例:[3,1,6,2,7]排序后得到[1,2,3,6,7],并且相邻两数的最大差值是3(元素3和元素6的差值)。
解决思路:
利用到桶的思想,数组有n个数,定义n+1个桶,遍历数组找到最小值和最大值,放在桶的前后两端。然后min-max范围之间的数,分成n+1份,元素属于哪个范围,就放到哪个桶内。例子:设数组长度为9,最小值是0,最大值是99!则需要准备10个桶,0放在0号桶,99放在9号桶。则0-99范围的数分成10份,即0-9、10-19、......、90-99这10个区间,也就是10个桶。所以元素属于哪个范围,就放到哪个桶内。
因为首尾两个桶必不为空,则中间至少有一个空桶。则排序后的相邻两个数既可能来自同一个桶,也可能来自不同桶。则依据该空桶,找到该空桶左边最近的非空桶和右边最近的非空桶,此时左边非空桶中的最大值和右边非空桶中的最小值,在排序后绝对是相邻的,而此时这两个极值的差值又绝对比桶的范围要大,也就是说肯定比一个桶内的两个元素差值大。所以说,最大差值只能来自不同的桶内元素。因此只需要统计每个桶内的最小值和最大值即可,不用记录进入桶内的每一个元素。但是,需要注意的是,空桶两侧的非空桶差值不一定就是最大的差值。比如:桶10-19内只有值为19的元素,桶20-29为空,桶30-39内只有值为30的元素,桶40-49内只有值为49的元素,此时很明显最大差值不来自于空桶两边的非空桶。故在遍历桶的时候,遇到空桶的时候,就跳过;如果是非空桶,就寻找下一个非空桶,记录其差值,并与最大差值进行比较,遍历完即可。
代码实现:
package com.gxu.dawnlab_algorithm1$2;
/**
* 给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。
* @author junbin
*
* 2019年4月12日
*/
public class MaxGap {
public static int maxGap(int[] arr){
if(arr == null || arr.length < 2){
return 0;
}
int len = arr.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
min = Math.min(min, arr[i]);
max = Math.max(max, arr[i]);
}
if(min == max){
return 0;
}
boolean[] hasNum = new boolean[len+1];
int[] maxs = new int[len+1];
int[] mins = new int[len+1];
int bid = 0; //几号桶
//分桶并对比
for (int i = 0; i < len; i++) {
bid = bucket(arr[i],len,min,max);//确定当前数属于几号桶
mins[bid] = hasNum[bid]?Math.min(mins[bid], arr[i]):arr[i];//更新该桶最小值
maxs[bid] = hasNum[bid]?Math.max(maxs[bid], arr[i]):arr[i];//更新该桶最小值
hasNum[bid] = true;
}
int res = 0;//最终结果
int lastMax = maxs[0];//距离当前桶左边最近的非空桶
for(int i = 1;i<len+1;i++){
if(hasNum[i]){
res = Math.max(res, mins[i]-lastMax); //当前桶最小值减去左边最近非空桶的最大值
lastMax = maxs[i];
}
}
return res;
}
public static int bucket(int num,int len,int min,int max){
return (int)(num - min) * len / (max - min);//((num-min)/(max-min))*len
}
}