【问题标题】:Dividing array so sum of subarray maximums is minimised划分数组,使子数组最大值的总和最小化
【发布时间】:2020-12-22 21:35:23
【问题描述】:

我只能想出一种蛮力的方法来解决这个问题。有兴趣看看 Algo SO 社区会想出什么。

给出一个 arr a 和一个整数 x (1s1, s2....sx ,这样max(s1) + max(s2)....+ max(sx) 的和是子数组和的所有可能组合中的最小值(参见下面的示例)。返回一个具有 x-1 个索引的数组,其中包含索引 i(不包括),其中发生拆分 a[0:i], a[i+1:i2], a[i2+1: i3].....a[ix:]

例子:

a = [10,30,40,20,50]
x = 2
return = [1]

将索引 1 处的数组拆分为 [10] 和 [30,40,20,50]

将导致max([10]) + max([30,40,20,50]) = 60,这是所有其他拆分数组的方法中的最小值。

其他可能的拆分 -

  • 不能在索引 0 处拆分,因为那样只会产生 1 个数组和 x = 2
  • 在索引 2 处拆分 = max([10,30]) + max([40,20,50]) = 80
  • 在索引 3 处拆分会产生 90
  • 在索引 4 处拆分会产生 90
  • 不允许在索引 5 处拆分,因为这样只会产生 1 个数组和 x = 2

【问题讨论】:

  • 我会更新问题。子数组可能组合的所有可能总和中的最小值(拆分数组的其他方法)
  • 所以你不想要蛮力?
  • @adirabargil 蛮力需要指数级的时间。动态编程采用O(len(a) * x)。即使使用速度很快的计算机,您通常也不需要蛮力。
  • 你不会总是在列表末尾划分最小的项目以便将较大的项目组合在一起吗?给定一个大于 1 的n,您将把最小的项目从末尾分区,以相同的方式递归地寻址其余的项目?
  • @MarkMeyer,arr 不应重新排序。我将添加说明

标签: python algorithm sorting


【解决方案1】:

这是一个动态规划问题。

首先建立如下数据结构。

for each position in the array
    for each count of how many splits
        (current max, sum of maxes, position of last split)

例如,在您的问题中,数据结构如下所示:

[   # One group, no splits
    [(10, 10, 0)], # 2 groups, 1 split
    [(30, 30, 0), (30, 40, 1)],
    [(40, 40, 0), (40, 50, 1), (40, 80, 2)],
    [(40, 40, 0), (20, 50, 1), (20, 70, 3)],
    [(50, 50, 0), (50, 60, 1), (50, 100, 2)], # the choices are equal
]

这可以通过简单的双循环来创建。你从[[(a[0], a[0], 0)]]开始。要找出 i, j 条目,您必须考虑在 (i-1, j-1) 条目之后开始一个新组,或者将当前元素添加到 (i-1, j) 条目中的最后一个组。

一旦你有了这个,从数组的最后一个位置和所需的分割数开始。数据结构告诉你最后一次分裂在哪里,你去那个位置,向下一个分裂,找到前一个分裂的地方。沿着那个 cookie crumb trail 往回走,你会发现所有的裂痕。

在您的示例中,(len(a), x) 条目位于 (4, 1) 并具有值 (50, 60, 1)。上一个条目位于 (1, 0) 并具有值 (10, 10, 0)。我们忽略边界处的分割,得到[1]作为答案。

如果你想创建x=3,那么你可以从(50, 100, 2)开始,返回(40, 50, 1),然后返回(10, 10, 0),得到[1, 2]的答案。

【讨论】:

  • 您能详细说明一下您的算法吗? Db 的基本情况是什么,metrix 是如何填充的?我没有关注这部分(current max, sum of maxes, position of last split)
  • @OLIVER.KOO 我添加了您的示例问题作为示例。
  • 注意,我的“选择是平等的”评论是因为第二次在 2 和 3 处拆分得到相同的总数。
  • @גלעדברקן 子数组是连续的,因此不能尝试多个组。所以只有两种可能性 - 开始一个新组或扩展前一个组。如果我们扩展前一组,我们只需要知道 A[i] 是小于现有的最大值(在这种情况下,最大值和最大值之和相同)还是更大(在这种情况下,最大值和最大值之和串联增加) )。
  • @גלעדברקן 由于找到新的最大值而将组开始向右移动可能会产生两个结果。要么它不改变前一组的最大值,在这种情况下无关紧要,要么它确实改变了前一组的最大值,在这种情况下总和变得更糟。无论哪种方式,都没有理由将其向右移动。
【解决方案2】:

为了使 Max(sx) 变得尽可能小,我们应该始终努力创建具有尽可能小的数字的子数组。因此,只需将输入列表划分为具有最小可能值的单元素列表:

arr = [10,30,40,20,50]
n = 2
a = []

def subarray(a):
    r = []
    while (len(r) <= n-2):
        r.append(a[:1])
        a = a[1:]
    r.append(a)
    return r

def calculate(a):
    r = 0
    for i in subarray(a):
        r += i[-1]
    return r

print(calculate(arr))

【讨论】:

    【解决方案3】:

    如果你可以构造它以使用记忆,蛮力并不是那么糟糕。

    递归地进行左右拆分以确定最佳解决方案的方法,将每个左侧前缀与右侧少拆分的最佳解决方案相结合。由于这将多次计算相同的右侧拆分,因此记忆化(将过去的结果保存在内存中以供重用)将使大多数探索分支短路。

    from functools import lru_cache
    from itertools import accumulate
    def minMaxSum(A,x):
        
        @lru_cache()                # memoization for
        def findBest(start,splits): # best split from start position to end of A
    
            R = A[start:] # right-side/remaining elements
    
            # base cases (no recursion)
            if splits == 1:      return max(R),[R]
            if splits == len(R): return sum(R),[[r] for r in R]
            
            # progressively increasing size of left side subarray
            bestSum   = sum(R)+1           
            maxLeft   = 0
            for i,r in enumerate(R[:len(R)-splits+1],1):
                if r >= bestSum: break   # won't improve on best at this point
                maxLeft = max(maxLeft,r) # max for left side          
                subSum,subArrays = findBest(start+i,splits-1) # recurse right side
                subSum += maxLeft
                if subSum>=bestSum: continue # track improvement to best solution
                bestSum   = subSum
                bestSplit = [R[:i],*subArrays] # left side + best right side solution
            return bestSum,bestSplit
        
        # convert to break indexes and return solution
        maxSum,subArrays = findBest(0,x)
        breaks = list(accumulate(map(len,subArrays)))[:-1]
        return breaks,maxSum,subArrays
                
    

    输出:

    print(*minMaxSum([10,30,40,20,50],2))
    # [1] 60 [[10], [30, 40, 20, 50]]
        
    print(*minMaxSum([10,30,40,20,50],3))
    # [1, 2] 90 [[10], [30], [40, 20, 50]] 
    
    print(*minMaxSum([40,30,40,20,50],3))
    # [3, 4] 110 [[40, 30, 40], [20], [50]]
    
    print(*minMaxSum([40,25,30,45,40,20,35,50],5))
    # [1, 2, 5, 6] 180 [[40], [25], [30, 45, 40], [20], [35, 50]]
    
    A = [i%5+1 for i in range(37)]
    x = 11
    print(*minMaxSum(A,x))
    # [1, 2, 5, 6, 7, 10, 11, 12, 35, 36] 27
    # [[1], [2], [3, 4, 5], [1], [2], [3, 4, 5], [1], [2],
    # [3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], [1], [2]]
    

    【讨论】:

      猜你喜欢
      • 2021-12-09
      • 1970-01-01
      • 2014-08-07
      • 2020-10-23
      • 1970-01-01
      • 1970-01-01
      • 2017-02-02
      • 2016-05-27
      • 1970-01-01
      相关资源
      最近更新 更多