【Lintcode】1859. Minimum Amplitude

题目地址:

https://www.lintcode.com/problem/minimum-amplitude/description

给定一个数组,定义振幅为数组最大值与最小值之差。每一步操作允许将数组中的某个数变为任意一个数。问对该数组做三次操作,能达到的最小振幅是多少。

主要思路是这样的。由于改变数字要使得振幅减少,所以我们倾向于改变最大值或者最小值,所以我们先对数组排序。接下来的问题是怎么改。如果数组长度小于等于 4 4 ,那么只需要将其中的数全改成和某一个一样即可,此时振幅变为 0 0 ,是最优情况。否则我们可以猜出,只需穷举删除两端数字的所有情况即可。而删除两端的三个数,之后得到的振幅是非常方便计算的。例如,对于 ( 0 , 1 , 5 , 10 , 14 ) (0, 1, 5, 10, 14) ,我们可以枚举右边删三个左边不删,得 1 0 = 1 1-0=1 ,再枚举右边删两个左边删一个,得 5 1 = 4 5-1=4 ,再枚举右边删一个左边删两个,得 10 5 = 5 10-5=5 ,最后枚举左边删三个得 14 10 = 4 14-10=4 。我们看出,只需从左向右算长度为 5 3 = 2 5-3=2 的区间的振幅即可。详细证明附在后面,代码如下:

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;
    }
}

时间复杂度 O ( n log n ) O(n\log n) ,空间 O ( 1 ) O(1)

算法正确性证明:
我们可以证明一般的结论。先证明,在数组长度为 n n 且已排序的情况下,枚举左右端总共删除 k k 个数( n > k n>k )的 k + 1 k+1 种情况即可得到正确答案。
反证法:假设按照上面的方法得到的答案不能枚举到最优的解,也就是说存在一种上述删除方法之外的方法能得到更小的振幅。那么意味着,这些删除的数字中至少存在一个数,要么其左边存在未删除的数字,要么其右边存在未删除的数字(如果不存在这样的数的话,意味着删除的数字全是在边界处且构成一条”线段“,而这样的删除方式是会被枚举到的,与假设矛盾),不妨设 x x 右边有未删除的数字。我们构造出一个更优方案,在删除 x x 时,我们不选择删 x x ,而选择删 x x 右边的那个未删除的数中最右边的那个数,这样就得到了更小的振幅,与假设矛盾。所以结论正确。

接下来注意到一个显然的结论,给 k k 次删数的机会,会得到的最小振幅不会比给改 k k 个数的机会,得到的最小振幅更大。因为数字越少,显然振幅就越小,所以删数只会得到更好的解。

最后的逻辑只需要证明,改 k k 个数能得到删 k k 个数的最优解即可。因为 n > k n>k ,所以可以把删数的操作转变为,将所删的数改成最后最优解中未被删除的数中的其中一个。这样就可以达到删 k k 个数的最优解。

综上所述,证明完毕。

发布了354 篇原创文章 · 获赞 0 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/105021782