问题描述:
数组中的一个数字减去它右边子数组中的一个数字可以得到一个差值,求所有可能的差值中的最大值
例如:{1,4,17,3,2,9} 最大的差值为17-2=15
分析:
(1)“蛮力”法
首先,遍历数组,找到所有的差值,然后,从所有差值中找出最大值。
具体实现方法为:针对数组a中的每一个元素a[i], 求所有a[i] - a[j] (i<j<n)的值中的最大值。
代码:
public int getMax(int[] a){
if(a==null || a.length<=1){
return Integer.MIN_VALUE;
}
int len = a.length;
int max = Integer.MIN_VALUE;
for(int i=0; i<len-1; i++){
for(int j=i+1; j<len; j++){
if(a[i] - a[j] > max){
max = a[i] - a[j];
}
}//
}//
return max;
}
(2)二分法
把数组分为两个子数组,那么最大的差值只有三种可能:
1)最大的差值对应的被减数和减数都在左子数组中,假设最大差值为 leftMax
2)最大的差值对应的被减数和减数都在右子数组中,假设最大差值为 rightMax
3)被减数是左子数组的最大值,减数四右子数组中的最小值,假设差值为 mixMax
那么就可以得到这个数组的最大差值为这3个差值的最大值,即 max(leftMax, rightMax, mixMax)
这种方法对数组只经过一次遍历,时间复杂度为O(n),但是采用了递归的实现方式,在递归调用时要进行压栈和弹栈操作,因此有额外的开销,会导致算法的性能有所下降。在实现时,为了通过传递引用的方式获取数组的最大值与最小值,使用了AtomicInteger而不是Integer类。主要原因为Integer类虽然也可以传递引用,但是他是不可变量,在方法内部不能对其进行修改。
代码:
/**
* 二分法
* max = (leftMax, rightMax, mixMax)
*/
public int getMax1(int[] a){
if(a == null){
return Integer.MIN_VALUE;
}
int len = a.length;
if(len <= 1){
return Integer.MIN_VALUE;
}
AtomicInteger max = new AtomicInteger(0);
AtomicInteger min = new AtomicInteger(0);
return getMaxDiff(a,0,len-1,max,min);
}
private int getMaxDiff(int[] a, int begin, int end, AtomicInteger max, AtomicInteger min) {
if(begin==end){
max.set(a[begin]);
min.set(a[begin]);
return Integer.MIN_VALUE;
}
int middle = begin + (end-begin)/2;
// 数组前半部分的最小值与最大值
AtomicInteger lMax = new AtomicInteger(0);
AtomicInteger lMin = new AtomicInteger(0);
// 数组前半部分的最大差值(第一种情况)
int leftMax = getMaxDiff(a, begin, middle, lMax, lMin);
// 数组后半部分的最小值与最大值
AtomicInteger rMax = new AtomicInteger(0);
AtomicInteger rMin = new AtomicInteger(0);
// 数组前半部分的最大差值(第二种情况)
int rightMax = getMaxDiff(a, middle+1, end, rMax, rMin);
// 对应第三种情况
int mixMax = lMax.get() - rMin.get();
// 求数组的最大值与最小值
if(lMax.get() > rMax.get()){
max.set(lMax.get());
}else
max.set(rMax.get());
if(lMin.get() < rMin.get()){
min.set(lMin.get());
}else
min.set(rMin.get());
// 求最大的差值
int allMax = (leftMax > rightMax) ? leftMax : rightMax;
allMax = (allMax > mixMax) ? allMax : mixMax;
return allMax;
}
(3)动态规划
给定数组a,申请额外的数组diff和max, 其中diff[i] 是以数组第 i 个数字为减数的所有数对之差的最大值(前 i + 1 个数组成的子数组中最大的差值), max[i] 为前 i+1 个数的最大值。 假设已经求得了 diff[i] , diff[i + 1] 的值有两种可能性
1)等于diff[i]
2)等于 max[i] - a[i]
可以得到: diff[i+1] = max( diff[i] , max[i-1] - a[i])
max[i+1] = max( max[i], a[i+1])
数组最大的差值为diff[n-1] (n为数组的长度)
对数组进行了一次遍历,时间复杂度为O(n),由于没有采用递归的方式,因此相比上一个方法,在性能上有所提升。由于引入了两个额外的数组,因此这个算法的空间复杂度为O(n)
代码:
public int getMax3(int[] a){
if (a==null){
return Integer.MIN_VALUE;
}
int len = a.length;
if(len <= 1){
return Integer.MIN_VALUE;
}
int[] diff = new int[len];
int[] max = new int[len];
diff[0] = Integer.MIN_VALUE;
max[0] = a[0];
for(int i=1; i<len; i++){
diff[i] = max(diff[i-1], max[i-1]-a[i]);
max[i] = max(max[i-1],a[i]);
}//
return diff[len-1];
}
private int max(int i, int j) {
return i>j ? i:j;
}
动态规划优化:
在求解 diff[ i+1 ] 时,只用到了 diff[i] 与max[i] ,而与数组 diff 和 max 中其他数字无关,因此可以通过两个变量而不是数组来记录 diff[i] 与 max[i] 的值,从而降低了算法的空间复杂度。
代码:
public int getMax2(int[] a){
if (a==null){
return Integer.MIN_VALUE;
}
int len = a.length;
if(len <= 1){
return Integer.MIN_VALUE;
}
int diff = 0;
int max = a[0];
for(int i=1; i<len; i++){
diff = max(diff, max-a[i]);
max = max(max,a[i]);
}//
return diff;
}
(4)最大子数组求和的方法
给定一个数组a,数组长度为n,额外申请一个长度为n-1的数组diff, 数组diff 中的值满足 diff[i] = a[i] - a[i-1], 那么a[i] -a[j] (0<i<j<n)就等价于 diff[i] +diff[i+1] + ... + aidd[j]. 因此求所有a[i] - a[j] 组合的最大值就可以转换为求解所有 diff[i] + diff[i+1] + ... + diff[j] 组合的最大值。