目录
1. 问题描述
给你一个整数数组 nums
,和一个整数 k
。对于每个下标 i
(0 <= i < nums.length
),将 nums[i]
变成 nums[i] + k
或 nums[i] - k
。
nums
的 分数 是 nums
中最大元素和最小元素的差值。在更改每个下标对应的值之后,返回 nums
的最小 分数 。
示例 1:
输入:nums = [1], k = 0 输出:0 解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。
示例 2:
输入:nums = [0,10], k = 2 输出:6 解释:将数组变为 [2, 8] 。分数 = max(nums) - min(nums) = 8 - 2 = 6 。
示例 3:
输入:nums = [1,3,6], k = 3 输出:3 解释:将数组变为 [4, 6, 3] 。分数 = max(nums) - min(nums) = 6 - 3 = 3 。
提示:
1 <= nums.length <= 10^4
0 <= nums[i] <= 10^4
0 <= k <= 10^4
2. 解题分析
粗看,心中疑惑,为什么会有这么简单的medium?
及至草草写了几行代码然后测试了一下,才意识到对问题的理解完全错了。天真的错误代码如下:
class Solution:
def smallestRangeII(self, nums: List[int], k: int) -> int:
min_num = 2*10**4
max_num = -10**4
for i in range(len(nums)):
trial0 = nums[i]+k
trial1 = nums[i]-k
min_num = min(min_num, trial0, trial1)
max_num = max(max_num, trial0, trial1)
return max_num - min_num
以上代码错在只是求除了可能的最大值与可能的最小值之间的差值。而问题要求的是可能的最大值与可能的最小值之间的差值的最小值!
对每个数据有两种修改方式,因此总共会生成种修改序列,问题要求的是这种序列的最大值和最小值的差值中的最小值。换句话说要找到所有最大值中的最小值,和所有最小值的中的最大值,然后求两者之差,所以理论上甚至可能出现负数。但是种修改序列分别求,这是指数复杂度,显然是无法接受的。
可能会想到,所有最大值中的最小值不就是将所有数都减3后的最大值吗,所有最小值中的最大值不就是所有数都减3后的最小值吗?貌似很有道理,但是遗憾的是,这两者不一定会出现在一个序列中。所以不能这么简单地考虑。
由于本题的结果不依赖于数据的排序位置,所以可以考虑先将输入数据进行排序处理(比如说,从小到大排序)。从直感出发,要得到最小的分数,最大值应该出现在排序数组的后半段减的结果,最小值应该出现在排序数组的前半段加的结果。
可以这样来理解,针对任意的一个(基于排序后数组,记为)修改后的序列,假设第一个减的索引为,那么索引之后的任何元素如果原本是加 的改为减的话都不会让最大值变大。因此所得的分数不可能比当前这个序列更大。因此所有第一个减 的元素为索引的序列中, 索引之后元素全部为减 的序列的分数肯定是最小的。
基于这个insight,问题简化为:基于排序后数组,以某个为界,索引之前的数据都加,之后(包含)的数据都减,所得的分数记为,而所要求的最小分数即为。
进一步,这样一个序列中的最大值一定是在,而最小值则为。对进行扫描,执行以上计算即可得所求结果。
基于排序后数组的搜索的复杂度为,而排序的复杂度为,所以总的时间复杂度是。
3. 代码实现
from typing import List
class Solution:
def smallestRangeII(self, nums: List[int], k: int) -> int:
n = len(nums)
nums.sort()
maxscore = 2*10**4
for j in range(n+1):
if j == 0 or j == n:
score = nums[n-1] - nums[0]
else:
score = max(nums[n-1]-k,nums[j-1]+k) - min(nums[0]+k,nums[j]-k)
maxscore = min(maxscore,score)
return maxscore
if __name__ == "__main__":
sln = Solution()
nums = [1]
k = 0
print(sln.smallestRangeII(nums, k))
nums = [0,10]
k = 2
print(sln.smallestRangeII(nums, k))
nums = [1,3,6]
k = 3
print(sln.smallestRangeII(nums, k))
执行用时:84 ms, 在所有 Python3 提交中击败了56.99%的用户
内存消耗:15.9 MB, 在所有 Python3 提交中击败了42.47%的用户
另外参见:Leetcode0908. 最小差值 I(simple)
908 vs 910: 前者的约束[-k,k]的范围,后者的约束为对于给定的k只能做+k或者-k的调节,第一感或许会觉得可选择范围更小了,那不是更简单嘛。其实不然,本题与910的对比类似于线性规划与整数规划问题的对比。整数规划是线性规划的一个子类,但是比线性规划难,因为选择范围更小从另一个角度来理解就意味着约束条件更紧,这就导致找到满足约束条件的最优解更难。