使数组互补的最少操作次数

标签: 数组 哈希表 前缀和

难度: Medium

给你一个长度为 偶数 n 的整数数组 nums 和一个整数 limit 。每一次操作,你可以将 nums 中的任何整数替换为 1 到 limit 之间的另一个整数。

如果对于所有下标 i下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 inums[i] + nums[n - 1 - i] = 5

返回使数组 互补最少 操作次数。

 

示例 1:

输入:nums = [1,2,4,3], limit = 4
输出:1
解释:经过 1 次操作,你可以将数组 nums 变成 [1,2,2,3](加粗元素是变更的数字):
nums[0] + nums[3] = 1 + 3 = 4.
nums[1] + nums[2] = 2 + 2 = 4.
nums[2] + nums[1] = 2 + 2 = 4.
nums[3] + nums[0] = 3 + 1 = 4.
对于每个 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互补的。

示例 2:

输入:nums = [1,2,2,1], limit = 2
输出:2
解释:经过 2 次操作,你可以将数组 nums 变成 [2,2,2,2] 。你不能将任何数字变更为 3 ,因为 3 > limit 。

示例 3:

输入:nums = [1,2,1,2], limit = 2
输出:0
解释:nums 已经是互补的。

 

提示:

  • n == nums.length
  • 2 <= n <= 105
  • 1 <= nums[i] <= limit <= 105
  • n 是偶数。

Submission

运行时间: 159 ms

内存: 30.8 MB

class Solution:
    def minMoves(self, nums: List[int], limit: int) -> int:
        n = len(nums)
        diff = [0] * (limit*2 + 1)
        for i in range(n//2):
            a,b = nums[i], nums[-1-i]
            if a > b:
                a,b = b,a 
            diff[1] += 2
            diff[a] -= 1
            diff[a+b-1] -= 1
            diff[a+b] += 1
            diff[b+limit] += 1

        return min(accumulate(diff[1:]))

Explain

此题解利用了差分数组的技巧来优化区间更新的操作。差分数组diff用于记录操作的变化:初始时假设每对(i, n-1-i)需要两次操作来达到互补状态,这是diff数组全体加2的原因。对于每对a和b(其中a是较小的那个以简化区间操作),首先减去一次操作,因为他们已经共同贡献了一个和。然后再根据可能的最小和和最大和调整diff数组,以确保最少的修改次数覆盖更多的配对。最后,通过累积和找出最少操作次数,即差分数组的最小累积值。

时间复杂度: O(n + limit)

空间复杂度: O(limit)

class Solution:
    def minMoves(self, nums: List[int], limit: int) -> int:
        n = len(nums)  # 获取数组长度
        diff = [0] * (limit*2 + 1)  # 初始化差分数组
        for i in range(n//2):  # 只遍历到数组的中间
            a, b = nums[i], nums[-1-i]  # 取一对互补的元素
            if a > b:  # 确保a是较小的一个
                a, b = b, a
            diff[1] += 2  # 假设每对初始需要两次操作
            diff[a] -= 1  # 减少因共同值减少的一次操作
            diff[a+b-1] -= 1  # 区间操作的开始
            diff[a+b] += 1  # 调整区间差分
            diff[b+limit] += 1  # 区间操作的结束
        # 通过累积和计算最少的操作次数
        return min(accumulate(diff[1:]))

Explore

在算法中,初始时`diff[1]`增加2是基于假定每对元素(i, n-1-i)最初都需要两次操作来达到互补状态。这是考虑到在最坏的情况下,每对元素可能需要分别调整两次才能达到任何可能的目标和值。因此,算法开始时为每对元素预设了两次操作,然后再通过后续的差分操作来根据实际的元素值进行调整,以减少不必要的操作次数。

在差分数组中,`diff[a+b-1]`和`diff[a+b]`之间的调整是为了有效地处理区间更新,这种调整方式是区间加法的常用技巧。通过在`diff[a+b-1]`处减1,我们开始减少从这点开始的操作次数,而在`diff[a+b]`处加1则是结束这个减少的区间。这意味着对于所有和值在a+b至b+limit之间的元素对,其所需的操作次数会被减少。这种差分处理使得累积和的计算可以快速地反映出各个和值所需的最终操作次数,从而找出最少操作次数。

算法在处理边界条件时,通过精心设计`diff`数组的大小和索引的调整来确保不会超出数组的有效范围。`diff`数组的长度被设置为`limit*2 + 1`,这是因为最大可能的和为`2*limit`(当两个最大元素相加时)。通过在计算差分时考虑元素值与`limit`的关系,并且在更新差分数组时始终检查索引是否合法(例如,确保在加上`limit`之后的索引仍然在数组范围内),算法避免了数组越界的风险。