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

本文介绍了一种通过构造差分数组来解决给定整数数组使其满足互补条件的问题,通过分析目标和与数组元素的关系,确定最少操作次数。
摘要由CSDN通过智能技术生成

这是数组的第23篇算法,力扣链接

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

如果对于所有下标 i下标从 0 开始),nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums 是 互补的 。例如,数组 [1,2,3,4] 是互补的,因为对于所有下标 i ,nums[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 已经是互补的。

首先这里引入一种解题思路,即差分数组:

差分数组是一种用来表示数列的方法,它能够高效地对原始数组的某个区间进行增减操作,同时能够快速计算出区间操作后的结果。差分数组通常用于解决数据的区间更新问题。

假设有一个初始数组 A,长度为 n,差分数组 B 也是一个长度为 n 的数组,其中 B[i] 表示的是 A[i] 和 A[i-1] 的差值,即 B[i] = A[i] - A[i-1]。对于数组 A 的第一个元素 A[0],我们可以设定 B[0] = A[0],因为没有 A[-1]

差分数组的主要优点是能够在 O(1) 的时间复杂度内对 A 的连续子区间进行增减操作。如果我们想将 A 中从 l 到 r (包括 l 和 r)的所有元素都加上一个值 x,我们只需要将 B[l] 增加 x,并将 B[r+1] 减去 x(如果 r+1 < n 的话)。这样做的效果是,当我们通过差分数组重新计算 A 的值时,只有从 l 到 r 的元素会被增加 x

要从差分数组 B 获取原始数组 A 的值,我们只需要对 B 进行一次前缀和计算即可。具体来说,A[0] = B[0],对于所有 i > 0A[i] = A[i-1] + B[i]

下面是一个简单的例子来说明差分数组的工作原理:

假设我们有一个数组 A = [2, 5, 3, 6],我们构建差分数组 B 如下:

A = [2,  5,  3,  6]
B = [2,  3, -2,  3]  // B[0]=A[0], B[1]=A[1]-A[0], B[2]=A[2]-A[1], B[3]=A[3]-A[2]

现在,如果我们想将 A 的第 2 个元素到第 3 个元素(从 0 开始计数)都加上 4,我们只需要对 B 做以下操作:

B[2] += 4  // 对应 A[2] 开始的地方加上 4
B[4] -= 4  // 对应 A[3] 后面的元素减去 4,由于这里 r+1 超出数组长度,所以这步可以省略

更新后的差分数组 B 为:

B = [2,  3,  2,  3]

通过差分数组 B,我们可以得到更新后的 A 数组:

A = [2,  5,  7,  10]  // 通过计算前缀和得到

如你所见,A[2] 和 A[3] 都成功地加上了 4。

差分数组是一种非常实用的数据结构,特别适用于那些需要频繁进行区间更新操作的场景。

有了差分数组,我们可以更直观的分析索引之间的数据值的变化,在这道题里可以充分应用。

然后开始分析题目:

假设数组左右的值为a,b,最终的目标和为target,分类讨论一下需要修改的次数:

  • 当 target < 1 + a 时,需要操作两次;
  • 当1 + a ≤ target < a + b 时,需要操作一次;
  • 当 target = a+b 时,不需要操作;
  • 当 a + b < target ≤ b + limit 时,需要操作一次;
  • 当 target > b + limit 时,需要操作两次。

 然后我们可以以最坏的情况做减法求取最小的修改次数:

  • 在 1+ a 处,操作次数减少一次;
  • 在 a + b 处,操作次数减少一次;
  • 在 a + b + 1 处,操作次数增加一次;
  • 在 b + limit + 1 处,操作次数增加一次。

因此,我们可以遍历数组中的所有数对,求出每个数对的这四个关键位置,然后更新差分数组。最后,我们遍历(扫描)差分数组,就可以找到令总操作次数最小的target,同时可以得到最少的操作次数。

func minMoves(nums []int, limit int) int {
	count := make([]int, limit*2+2)

	for i := 0; i < len(nums)/2; i++ {
		sum := nums[i] + nums[len(nums)-1-i]
		left := getMin(nums[i], nums[len(nums)-1-i])
		right := sum - left + limit
		count[left+1]--
		count[sum]--
		count[sum+1]++
		count[right+1]++
	}

	result, cur := len(nums), len(nums)
	for i := 2; i <= limit*2; i++ {
		cur += count[i]
		result = getMin(result, cur)
	}
	return result
}

func getMin(a, b int) int {
	if a < b {
		return a
	}
	return b
}

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值