问题描述
小C有一个长度为 n 的数组。她可以执行以下操作:选择一个下标 i,将 a_i 加到 a_{i-1} 或者 a_{i+1}(如果相邻的下标在范围内),然后移除元素 a_i。她想知道,最少需要进行多少次操作,才能使数组的所有元素相等。
测试样例
样例1:
输入:
n = 5 , a = [1, 4, 2, 3, 5]
输出:2
样例2:
输入:
n = 4 , a = [3, 3, 3, 3]
输出:0
样例3:
输入:
n = 6 , a = [1, 1, 1, 2, 2, 2]
输出:5
问题分析:
给定一个长度为 nn 的数组。我们可以进行以下操作:
- 选择一个下标 ii,将 aia_i 加到 ai−1a_{i-1} 或 ai+1a_{i+1}(如果相邻的下标在范围内),然后移除元素 aia_i。
目标是最少操作次数,使数组的所有元素相等。
关键观察:
- 总和不变性: 每次操作后,数组的总和保持不变。因为我们只是将一个元素的值加到相邻元素上,然后删除该元素。
- 划分问题转化: 问题可以转化为将数组划分为若干连续的段,使每段的和相等。最少的操作次数为 n−kn - k,其中 kk 是可以划分的段数。
- 可能的段和: 这些段的和为数组总和 SS 的因数。
解题思路:
-
计算数组总和: 计算 S=∑i=1naiS = \sum_{i=1}^{n} a_i。
-
找出 SS 的因数(不超过 nn): 这些因数代表可能的段数 kk。
-
尝试每个可能的段数 kk:
- 计算目标段和 target_sum=S/k\text{target_sum} = S / k。
- 尝试将数组划分为 kk 个连续段,每段的和为 target_sum\text{target_sum}。
- 使用贪心算法,累加元素值,当累加和等于 target_sum\text{target_sum} 时重置累加器。
- 如果成功划分(累加和在 kk 次达到 target_sum\text{target_sum},且不超过),更新最小操作次数为 n−kn - k。
-
输出最小操作次数: 在所有可能的 kk 中,选择最小的 n−kn - k。
时间和空间复杂度分析:
-
时间复杂度:
- 计算数组总和:O(n)O(n)。
- 找因数:O(S)O(\sqrt{S}),但由于 n≤105n \leq 10^5,因数数量有限。
- 对每个因数 kk 进行划分检查:每次 O(n)O(n)。
- 总时间复杂度:O(n×D)O(n \times D),其中 DD 是因数的数量,通常很小。
-
空间复杂度:
- 存储数组:O(n)O(n)。
- 存储因数集:O(D)O(D)。
- 总空间复杂度:O(n)O(n)。
代码实现:
def min_operations_to_equalize(n, a):
total_sum = sum(a)
max_operations = n - 1 # 最坏情况下,需要进行 n - 1 次操作
min_operations = max_operations
# 获取不超过 n 的总和的因数
def get_divisors(s, n):
divisors = set()
for i in range(1, int(s**0.5) + 1):
if s % i == 0:
if i <= n:
divisors.add(i)
if s // i <= n:
divisors.add(s // i)
return divisors
divisors = get_divisors(total_sum, n)
for k in divisors:
target_sum = total_sum // k
current_sum = 0
valid_partition = True
for num in a:
current_sum += num
if current_sum == target_sum:
current_sum = 0
elif current_sum > target_sum:
valid_partition = False
break
if valid_partition and current_sum == 0:
operations = n - k
min_operations = min(min_operations, operations)
return min_operations
# 示例使用:
if __name__ == "__main__":
# 测试样例
n1, a1 = 5, [1, 4, 2, 3, 5]
n2, a2 = 4, [3, 3, 3, 3]
n3, a3 = 6, [1, 1, 1, 2, 2, 2]
print(min_operations_to_equalize(n1, a1)) # 输出:2
print(min_operations_to_equalize(n2, a2)) # 输出:0
print(min_operations_to_equalize(n3, a3)) # 输出:5
代码说明:
- 函数
get_divisors: 获取不超过 nn 的 SS 的所有因数。 - 主循环: 遍历每个因数 kk,计算目标段和,并检查能否成功划分。
- 划分检查: 累加元素值,判断是否能在不超过目标段和的情况下成功划分。
- 更新最小操作次数: 如果成功划分,更新最小操作次数。
优化过程:
- 限制因数范围: 只考虑不超过 nn 的因数,减少计算量。
- 高效的划分检查: 使用贪心算法,一旦累加和超过目标段和,立即停止当前因数的检查,避免不必要的计算。