【发布时间】:2022-03-01 02:19:28
【问题描述】:
我有一个正整数数组。例如:
[1, 7, 8, 4, 2, 1, 4]
“归约操作”找到平均值最高的数组前缀,并将其删除。这里,数组前缀是指一个连续的子数组,其左端是数组的开始,例如上面的[1]或[1, 7]或[1, 7, 8]。使用更长的前缀可以打破平局。
Original array: [ 1, 7, 8, 4, 2, 1, 4]
Prefix averages: [1.0, 4.0, 5.3, 5.0, 4.4, 3.8, 3.9]
-> Delete [1, 7, 8], with maximum average 5.3
-> New array -> [4, 2, 1, 4]
我会重复归约操作,直到数组为空:
[1, 7, 8, 4, 2, 1, 4]
^ ^
[4, 2, 1, 4]
^ ^
[2, 1, 4]
^ ^
[]
现在,实际上执行这些数组修改是不必要的;我只是在寻找将被此过程删除的前缀长度列表,例如上面的[3, 1, 3]。
计算这些前缀长度的有效算法是什么?
天真的方法是在每次迭代中从头开始重新计算所有总和和平均值,以实现 O(n^2) 算法——我在下面附上了 Python 代码。我正在寻找对这种方法的任何改进——最好是低于O(n^2) 的任何解决方案,但是具有相同复杂性但更好的常数因子的算法也会有所帮助。
以下是我尝试过的一些事情(没有成功):
- 动态维护前缀和,例如使用Binary Indexed Tree。虽然我可以在
O(log n)时间轻松更新前缀 sums 或找到最大前缀 sum,但我还没有找到任何可以更新 average,因为平均值中的分母正在变化。 - 重用之前的前缀平均值“排名”——这些排名可能会发生变化,例如在某些数组中,以索引
5结尾的前缀的平均值可能大于以索引6结尾的前缀,但在删除前3 个元素后,现在以索引2结尾的前缀可能有一个比以3结尾的平均值小。 - 寻找前缀结束的模式;例如,任何最大平均前缀的最右边元素始终是数组中的局部最大值,但不清楚这有多大帮助。
这是朴素的二次方法的有效 Python 实现:
from fractions import Fraction
def find_array_reductions(nums: List[int]) -> List[int]:
"""Return list of lengths of max average prefix reductions."""
def max_prefix_avg(arr: List[int]) -> Tuple[float, int]:
"""Return value and length of max average prefix in arr."""
if len(arr) == 0:
return (-math.inf, 0)
best_length = 1
best_average = Fraction(0, 1)
running_sum = 0
for i, x in enumerate(arr, 1):
running_sum += x
new_average = Fraction(running_sum, i)
if new_average >= best_average:
best_average = new_average
best_length = i
return (float(best_average), best_length)
removed_lengths = []
total_removed = 0
while total_removed < len(nums):
_, new_removal = max_prefix_avg(nums[total_removed:])
removed_lengths.append(new_removal)
total_removed += new_removal
return removed_lengths
编辑:最初发布的代码在使用 Python 的 math.isclose() 和浮点比较的默认参数而不是正确分数比较时出现了一个罕见的错误。这已在当前代码中修复。可以在此Try it online link 找到错误示例,如果您好奇的话,还有一个前言解释了导致此错误的确切原因。
【问题讨论】:
-
导致
float版本失败的数字有多大?我也尝试过使用Fraction而不是truediv的版本,但是如果我没记错的话,它会慢10 倍:-(。考虑添加我自己的轻量级Fraction类,但这会使代码大两倍左右(我不知道它是否会更快)。 -
嗯...实际上我怀疑是
math.isclose而不是floats 造成了麻烦。也许这个总和是浮点数而不是整数。真的很想看看发生错误的数据。 -
@Pychopath 是的,是
math.isclose()引起了问题:我刚刚针对您的解决方案测试了失败的测试用例,只有我原来的测试用例受到影响。我正在努力创建一个带有失败案例和我们所有解决方案的在线代码运行器(TIO,就像你的链接),现在。但是,我尚未测试您的新解决方案或任何仅对大型随机输入进行浮点数学比较的解决方案。我认为任何没有真正分数比较的解决方案很可能在某些输入上是不正确的。 -
使用 random 输入我怀疑他们仍然很有可能成功:-)。当然,您可以使用专门设计的输入使其失败,例如
[2**55, 2**55 - 1](demo)。 -
@Pychopath 我已经添加了测试用例的链接和更多解释。除了非常大的输入外,我认为您的解决方案通常会给出正确的答案。如需更多讨论,也许continue this in chat 会更好? :)
标签: python arrays algorithm data-structures