很有意思的一道数学推理题目, 剪枝以后解法也很简洁。
初看貌似需要把每个数跟其他数作比较。但排序以后可以发现情况大大简化:
对于任一对元素a[i] < a[j], a[i] - k和a[j] + k 的情况可以排除, 因为会产生比原值更大的差, 所以对于原有数组的最小值min最大值max, (min - k, max + k)的情况可以排除。剩下的三种情况, (min - k, max - k), (min + k, max + k) 和 (min + k, max - k),后两种等价于原值max - min, 我们可以把初始最大差值设为max - min, 确保最终结果不会比这个平凡值更坏。
对于最后一种情况(min + k, max - k), 需要继续分情况讨论。
方便起见,我们可以把所有元素都预先-k, 然后从最小元素开始,尝试依次把各元素+2*k.
我们可以证明,如果选择a[i] + 2 * k,那么之前任一元素a[j] (0 <= j < i)加上 2 * k,都不会使结果更坏。证明见此:
B = [A[0] + 2K, ...,A[i-1]+2K, A[i] + 0, A[i+1] +2K, ..., A[j]+2K, ..., A[j+1]+0, A[n-1]+0]
B' = [A[0] + 2K, ...,A[i-1]+2K, A[i] + 2K, A[i+1] +2K, ..., A[j]+2K, ..., A[j+1]+0, A[n-1]+0]
A[i]+2K is between A[i-1] + 2K and A[i+1] +2K, so it must stand in the range of B.
B' is not worse than B, it can be easily generalized to multiple elements added by 0 between the ones added by 2K.
于是当我们考虑a[i] + 2 * k时,可以假设之前的元素都已经加上了2 * k。
当前最大差值取决于:
1) 当前数列最小值:
可能为a[i+1]
a[i+1],...a[0]+2*k...a[n-1]...
或a[0]+2*k
a[0]+2*k..., a[i+1],...a[n-1]...
2) 以及当前数列最大值:
可能为a[i]+2*k
...a[n-1]...a[i]+2*k
或者a[n-1]
...a[i]+2*k...a[n-1]
因此我们只需遍历所有元素,计算当前元素加上2*k的以后的数组的最大差值,取其中的最小值即可。
public: int smallestRangeII(vector<int>& a, int k) { size_t len = a.size(); if(len <= 1) return 0; sort(a.begin(), a.end()); int front = a.front(), back = a.back(), d = back - front, start = front + 2 * k; if( k >= d) return d; int ans = d; for(size_t i = 0; i < len - 1; i++){ int lo = min(start, a[i + 1]), hi = max(a[i] + 2 * k, back); ans = min(ans, hi - lo); } return ans; }
参考:https://zhanghuimeng.github.io/post/leetcode-910-smallest-range-ii/