线性动态规划:最少操作使数组元素全部相等(每次操作可对连续k个元素加1或减1)
题目描述
给定一个整数数组nums和一个整数k,每次操作可以选择一个长度为k的连续子数组,将该子数组中的所有元素加1或减1。要求计算使数组中所有元素相等的最少操作次数。如果无法实现,返回-1。
解题过程
步骤1:问题分析
这个问题的核心在于理解操作的特殊性:
- 每次操作影响的是连续k个元素
- 操作可以加1也可以减1
- 目标是通过最少的操作次数让所有元素相等
首先考虑可行性:由于每次操作影响k个连续元素,如果k=1,显然总是可行的。对于k>1的情况,需要分析操作对数组元素的相对影响。
步骤2:关键观察
设目标值为target,考虑相邻元素之间的关系。对于任意位置i和i+1,如果i≥k-1,那么操作覆盖位置i的子数组与覆盖位置i+1的子数组有k-1个重叠元素。
更精确地,定义差分数组d[i] = nums[i] - nums[i-1](i≥1)。每次操作对区间[i, i+k-1]加1,相当于:
- d[i]增加1
- d[i+k]减少1(如果i+k存在)
这意味着操作实际上是在调整差分数组的值。
步骤3:可行性判断
对于k>1的情况,考虑数组的前k-1个元素。由于任何操作都无法单独改变前k-1个元素中的某一个而不影响其他元素,因此前k-1个元素必须能够通过整体调整达到相等。
更准确地说,对于所有i ≡ j (mod k)的位置,它们的值必须能够调整到相等。这是因为操作的影响在模k意义下是相关的。
步骤4:动态规划状态定义
定义dp[i][j]表示考虑前i个元素,且第i个元素经过j次操作调整后的最小操作次数。但这样状态空间太大,需要优化。
实际上,我们可以通过数学分析得到更优解。设目标值为t,那么对于每个位置i,需要的调整量是t - nums[i]。但由于操作是连续的,调整量必须满足某种模式。
步骤5:差分数组解法
更高效的方法是使用差分数组:
- 计算每个位置i的"相对调整量":对于i≥k,调整量必须满足某种递推关系
- 通过滑动窗口计算最小操作次数
具体算法:
- 对于i从0到n-k,计算需要将nums[i]调整到与nums[i+1]等值的操作次数
- 累加这些操作次数,同时考虑操作的影响范围
步骤6:具体实现
def minOperations(nums, k):
n = len(nums)
if k == 1:
# 每个元素可以独立调整
return sum(abs(nums[i] - nums[0]) for i in range(1, n))
# 检查可行性
for i in range(k-1, n):
if (nums[i] - nums[i-k+1]) % k != 0:
return -1
# 计算最小操作次数
operations = 0
diff = [0] * n
for i in range(n - k + 1):
# 当前需要调整的量
current_diff = nums[i] + diff[i]
target = nums[0] # 以第一个元素为目标
if i == 0:
adjust = target - current_diff
else:
# 后续元素必须与前一个元素保持特定关系
adjust = (nums[i-1] + diff[i-1] + k - 1) - current_diff
if adjust % k != 0:
return -1
operations += abs(adjust) // k
diff[i] += adjust
if i + k < n:
diff[i+k] -= adjust
# 验证最后k-1个元素是否相等
for i in range(n - k + 1, n):
if nums[i] + diff[i] != target:
return -1
return operations
步骤7:算法优化
上述解法可以进一步优化。实际上,最优解可以通过计算每个位置模k的余数分组,然后对每组分别求解:
- 将数组按位置模k的余数分组
- 对每组排序,找到中位数
- 计算每组元素到中位数的距离之和
def minOperationsOptimized(nums, k):
n = len(nums)
if k == 1:
median = sorted(nums)[n//2]
return sum(abs(x - median) for x in nums)
# 按模k的余数分组
groups = [[] for _ in range(k)]
for i, num in enumerate(nums):
groups[i % k].append(num)
# 检查每组是否可行
for group in groups:
if len(set(group)) > 1:
# 每组内的元素必须能调整到相等
if any((group[i] - group[0]) % k != 0 for i in range(1, len(group))):
return -1
total_ops = 0
for group in groups:
if not group:
continue
# 将组内元素调整到相同值
sorted_group = sorted(group)
median = sorted_group[len(sorted_group) // 2]
# 计算操作次数
ops = sum(abs(x - median) // k for x in sorted_group)
total_ops += ops
return total_ops
步骤8:复杂度分析
- 时间复杂度:O(n log n),主要来自排序操作
- 空间复杂度:O(n),用于存储分组结果
总结
这个问题展示了如何将连续操作问题转化为差分数组问题,进而通过分组和数学分析找到最优解。关键洞察是认识到操作在模k意义下的特殊性质,以及如何利用中位数性质最小化操作次数。