题目地址:
https://www.lintcode.com/problem/minimum-amplitude/description
给定一个数组,定义振幅为数组最大值与最小值之差。每一步操作允许将数组中的某个数变为任意一个数。问对该数组做三次操作,能达到的最小振幅是多少。
主要思路是这样的。由于改变数字要使得振幅减少,所以我们倾向于改变最大值或者最小值,所以我们先对数组排序。接下来的问题是怎么改。如果数组长度小于等于 ,那么只需要将其中的数全改成和某一个一样即可,此时振幅变为 ,是最优情况。否则我们可以猜出,只需穷举删除两端数字的所有情况即可。而删除两端的三个数,之后得到的振幅是非常方便计算的。例如,对于 ,我们可以枚举右边删三个左边不删,得 ,再枚举右边删两个左边删一个,得 ,再枚举右边删一个左边删两个,得 ,最后枚举左边删三个得 。我们看出,只需从左向右算长度为 的区间的振幅即可。详细证明附在后面,代码如下:
import java.util.Arrays;
public class Solution {
/**
* @param A: a list of integer
* @return: Return the smallest amplitude
*/
public int MinimumAmplitude(int[] A) {
// write your code here
if (A == null || A.length <= 4) {
return 0;
}
// 先排序,使得最大值最小值产生在两端
Arrays.sort(A);
int l = 0, r = A.length - 1;
// 初始化最终结果为无穷大,然后更新这个值
int res = Integer.MAX_VALUE;
// 分别枚举左端删i个数,右边删3 - i个数的振幅,更新res
for (int i = 0; i <= 3; i++) {
res = Math.min(res, A[r - (3 - i)] - A[l + i]);
}
return res;
}
}
时间复杂度 ,空间 。
算法正确性证明:
我们可以证明一般的结论。先证明,在数组长度为
且已排序的情况下,枚举左右端总共删除
个数(
)的
种情况即可得到正确答案。
反证法:假设按照上面的方法得到的答案不能枚举到最优的解,也就是说存在一种上述删除方法之外的方法能得到更小的振幅。那么意味着,这些删除的数字中至少存在一个数,要么其左边存在未删除的数字,要么其右边存在未删除的数字(如果不存在这样的数的话,意味着删除的数字全是在边界处且构成一条”线段“,而这样的删除方式是会被枚举到的,与假设矛盾),不妨设
右边有未删除的数字。我们构造出一个更优方案,在删除
时,我们不选择删
,而选择删
右边的那个未删除的数中最右边的那个数,这样就得到了更小的振幅,与假设矛盾。所以结论正确。
接下来注意到一个显然的结论,给 次删数的机会,会得到的最小振幅不会比给改 个数的机会,得到的最小振幅更大。因为数字越少,显然振幅就越小,所以删数只会得到更好的解。
最后的逻辑只需要证明,改 个数能得到删 个数的最优解即可。因为 ,所以可以把删数的操作转变为,将所删的数改成最后最优解中未被删除的数中的其中一个。这样就可以达到删 个数的最优解。
综上所述,证明完毕。