Leetcode0910. 最小差值 II(medium)

目录

1. 问题描述

2. 解题分析

3. 代码实现


1. 问题描述

给你一个整数数组 nums,和一个整数 k 。对于每个下标 i0 <= 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

        以上代码错在只是求除了可能的最大值与可能的最小值之间的差值。而问题要求的是可能的最大值与可能的最小值之间的差值的最小值!

        对每个数据有两种修改方式,因此总共会生成2^n种修改序列,问题要求的是这2^n种序列的最大值和最小值的差值中的最小值。换句话说要找到所有最大值中的最小值,和所有最小值的中的最大值,然后求两者之差,所以理论上甚至可能出现负数。但是2^n种修改序列分别求,这是指数复杂度,显然是无法接受的。

        可能会想到,所有最大值中的最小值不就是将所有数都减3后的最大值吗,所有最小值中的最大值不就是所有数都减3后的最小值吗?貌似很有道理,但是遗憾的是,这两者不一定会出现在一个序列中。所以不能这么简单地考虑。

        由于本题的结果不依赖于数据的排序位置,所以可以考虑先将输入数据进行排序处理(比如说,从小到大排序)。从直感出发,要得到最小的分数,最大值应该出现在排序数组的后半段减\text{k}的结果,最小值应该出现在排序数组的前半段加\text{k}的结果。

        可以这样来理解,针对任意的一个(基于排序后数组,记为\textit{A})修改后的序列,假设第一个减\text{k}的索引为\textit{j},那么索引\textit{j}之后的任何元素如果原本是加 \text{k}的改为减\text{k}的话都不会让最大值变大。因此所得的分数不可能比当前这个序列更大。因此所有第一个减 \text{k}的元素为索引\textit{j}的序列中, 索引\textit{j}之后元素全部为减 \text{k}的序列的分数肯定是最小的。

        基于这个insight,问题简化为:基于排序后数组,以某个j \in [0,n-1]为界,索引\textit{j}之前的数据都加\text{k},之后(包含\textit{j})的数据都减\text{k},所得的分数记为f(k),而所要求的最小分数即为\min\limits_{0\leq k \leq n-1}{f(k)}

         进一步,这样一个序列中的最大值一定是在max(A[n-1]-3, A[j-1]+3),而最小值则为min(A[0]+3, A[j]-3)。对\textit{j}进行扫描,执行以上计算即可得所求结果。

        基于排序后数组\textit{A}的搜索的复杂度为O(n),而排序的复杂度为O(nlogn),所以总的时间复杂度是O(nlogn)

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的对比类似于线性规划与整数规划问题的对比。整数规划是线性规划的一个子类,但是比线性规划难,因为选择范围更小从另一个角度来理解就意味着约束条件更紧,这就导致找到满足约束条件的最优解更难。 

         回到总目录:笨牛慢耕的Leetcode每日一题总目录(动态更新。。。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值