【问题标题】:Maximum sum sublist?最大总和子列表?
【发布时间】:2013-02-10 08:28:32
【问题描述】:

我对这个问题试图提出的问题感到困惑。

编写函数mssl()(最小和子列表),将列表作为输入 整数。然后它计算并返回最大和的总和 输入列表的子列表。最大和子列表是一个子列表 条目总和最大的输入列表的(切片)。空的 子列表定义为总和为 0。例如,最大和子列表 列表中的[4, -2, -8, 5, -2, 7, 7, 2, -6, 5][5, -2, 7, 7, 2] 并且其条目的总和是19

如果我要使用这个函数,它应该返回类似于

>>> l = [4, -2, -8, 5, -2, 7, 7, 2, -6, 5]
>>> mssl(l)
19
>>> mssl([3,4,5])
12
>>> mssl([-2,-3,-5])
0

我该怎么做?

这是我目前的尝试,但它没有产生预期的结果:

def mssl(x):
    ' list ==> int '
    res = 0
    for a in x:
        if a >= 0:
            res = sum(x)
        return res
    else:
        return 0

【问题讨论】:

  • 如果你无法解决头脑中的问题,那么你无法用计算机解决它。在编写任何代码之前,请尝试自己解决一些示例。当你有了一个可行的方法,然后编写算法。

标签: python algorithm


【解决方案1】:

实际上有一个非常优雅、非常有效的解决方案,使用动态编程。它需要 O(1) 空间,以及 O(n) 时间——这是无与伦比的!

A 定义为输入数组(零索引),将B[i] 定义为以i 结尾但不包括位置i(即所有子列表A[j:i])的所有子列表的最大总和。因此,B[0] = 0B[1] = max(B[0]+A[0], 0)B[2] = max(B[1]+A[1], 0)B[3] = max(B[2]+A[2], 0) 等等。那么,很明显,max(B[0], ..., B[n]) 简单地给出了解决方案。

由于每个B 值仅依赖于前一个B,我们可以避免存储整个B 数组,从而为我们提供了O(1) 空间保证。

通过这种方法,mssl 简化为一个非常简单的循环:

def mssl(l):
    best = cur = 0
    for i in l:
        cur = max(cur + i, 0)
        best = max(best, cur)
    return best

演示:

>>> mssl([3,4,5])
12
>>> mssl([4, -2, -8, 5, -2, 7, 7, 2, -6, 5])
19
>>> mssl([-2,-3,-5])
0

如果您也想要开始和结束切片索引,则需要跟踪更多信息(注意这仍然是 O(1) 空间和 O(n) 时间,只是有点毛):

def mssl(l):
    best = cur = 0
    curi = starti = besti = 0
    for ind, i in enumerate(l):
        if cur+i > 0:
            cur += i
        else: # reset start position
            cur, curi = 0, ind+1

        if cur > best:
            starti, besti, best = curi, ind+1, cur
    return starti, besti, best

这会返回一个元组 (a, b, c) 使得 sum(l[a:b]) == cc 是最大的:

>>> mssl([4, -2, -8, 5, -2, 7, 7, 2, -6, 5])
(3, 8, 19)
>>> sum([4, -2, -8, 5, -2, 7, 7, 2, -6, 5][3:8])
19

【讨论】:

  • 我怀疑是否有人正在阅读此线程,但对于列表[-23, 12, 21, -1, -5, 45, 3, -17, 16],这将返回结束拼接7,应该是6
  • @robban:结果是使用 Python 的切片表示法编写的,不包括端点。
  • 如果数组中的所有元素都是负数,此解决方案将不起作用。修改 B[i] 以包含第 i 个位置,并为初始化设置 B[0] = A[0]。这应该可以解决所有负面情况。
  • @apadana:如第三个示例所示,此解决方案允许空子列表(当整个数组为负数时具有最佳总和)。
  • @nneonneo:谢谢。我在想我们不能返回空列表。假设我们可以返回空列表,您的解决方案是正确的,这更有意义。
【解决方案2】:

这是the maximum sub-array problem。 Kadane 的算法可以在O(n) 时间和O(1) 空间内求解,具体如下:

def mssl(x):
    max_ending_here = max_so_far = 0
    for a in x:
        max_ending_here = max(0, max_ending_here + a)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

【讨论】:

    【解决方案3】:

    根据问题,如果列表中的所有元素均为负数,则应返回最大总和为“零”

    如果您希望输出为子数组的最大值(负数),那么下面的代码会有所帮助:

    In [21]: def mssl(l):
    ...:     best = cur = l[0]
    ...:     for i in range(len(l)):
    ...:         cur = max(cur + l[i], l[i])
    ...:         best = max(best, cur)
    ...:     return best
    

    例子:

    In [23]: mssl([-6, -44, -5, -4, -9, -11, -3, -99])
    Out[23]: -3
    In [24]: mssl([-51, -23, -8, -2, -6])
    Out[24]: -2
    

    对于正数和负数

    In [30]: mssl([4, -2, -8, 5, -2, 7, 7, 2, -6, 5])
    Out[30]: 19
    

    【讨论】:

    • 这是正确的吗? best = cur = l[0] 它应该是 0 否则你会得到错误的答案。
    • 不确定为什么所有其他人都以 0 开头,而不是像此解决方案那样的第一个元素。但是要做到这一点,您需要将循环更改为for i in range(1, len(l))。另外请不要使用l作为变量名。
    【解决方案4】:

    所以如果你了解什么是子列表(或切片,可以假设是同一个东西),切片是由开始索引和结束索引定义的。

    所以也许您可以尝试遍历所有可能的开始和结束索引并计算相应的总和,然后返回最大值。

    提示:起始索引可以在 0 到 len(given_list)-1 之间变化。结束索引可以从start_indexlen(given_list)-1。您可以使用嵌套的for 循环来检查所有可能的组合。

    【讨论】:

      【解决方案5】:

      这是一个使用 Kadane 算法的 Java 实现,它打印最大和的索引。该实现需要 O(n) 时间和 O(1) 空间。

      public static void maxSumIndexes(int[] a) {
      
          int size = a.length;
          if(size == 0) return;
      
          int maxAtIndex = a[0], max = a[0];
          int bAtIndex = 0;
          int b = 0, e = 0;
      
          for(int i = 1; i < size; i++) {
              maxAtIndex = Math.max(a[i], a[i] + maxAtIndex);
              if(maxAtIndex == a[i])
                  bAtIndex = i;
      
              max = Math.max(max, maxAtIndex);
              if(max == maxAtIndex) {
                  e = i;
                  b = (b != bAtIndex)? bAtIndex : b;
              }
          }
      
          System.out.println(b);
          System.out.println(e);
      }
      

      【讨论】:

        【解决方案6】:

        简单的解决方案是遍历列表,然后尝试将切片相加,直到找到最好的切片。在这里,我还包括返回实际子列表的选项,默认情况下这是 False。我为此使用了 defaultdict,因为它比查找更简单。

        from collections import defaultdict
        
        def mssl(lst, return_sublist=False):
            d = defaultdict(list)
            for i in range(len(lst)+1):
                for j in range(len(lst)+1):
                    d[sum(lst[i:j])].append(lst[i:j])
            key = max(d.keys())
            if return_sublist:
                return (key, d[key])
            return key
        
        print mssl([4, -2, -8, 5, -2, 7, 7, 2, -6, 5])
        19
        print mssl([4, -2, -8, 5, -2, 7, 7, 2, -6, 5], True)
        (19, [[5, -2, 7, 7, 2]])
        

        奖励:列表理解方法:

        def _mssl(lst):
            return max( sum( lst[i:j] ) for i in xrange(len(lst)+1) for j in xrange(i, len(lst)+1) )
        

        【讨论】:

        • 请注意,此解决方案在存储列表的时间和数量上都是 O(n^2),因此需要 O(n^3) 内存。通过仅存储最高和子列表及其开始和结束索引的总和来修改它以占用 O(1) 内存是微不足道的。
        • 正确,这是一种懒惰、低效的方法。这是一个简单的例子来说明可以做什么。我会用一个简单的列表理解来解决这个问题。
        • 列表推导仍然生成长度为 n^2 的列表。取消括号使其成为生成器表达式,它将成为常量记忆。顺便说一句,这些实际上是 O(n^3) 时间,因为每个列表总和需要 O(n)。
        • @Dougal 谢谢,我知道,在我写max() 之前写了列表理解,我忘了删除括号。
        • 另外,出于实际目的:j 应该在 range(i, len(lst)) 中(或者 i+1 如果您在其他地方处理过空数组的情况),以避免 O(n^2) 无意义的空总和切片。 :)
        【解决方案7】:

        它要求您选择列表的较小部分,以便较小部分的总和最大。

        如果列表都是正数[1 2 3],那么总和最大的部分当然就是整个列表[1 2 3]的总和,即6。

        如果列表都是负数[-1 -2 -3],那么总和最大的小节就不是[],总和为0。

        但是,如果列表有正面和负面,则更难做出决定

        [1 2 3 -100 3 4 5] 你应该看到[3 4 5] 并返回 12

        [1 2 3 -2 3 4 5]你应该使用它并返回16

        【讨论】:

          【解决方案8】:

          我假设这是在产生最大和的数组中找到子序列的问题。我在搜索最大和 SUBSET 问题时遇到了这个问题。

          这个问题的java实现:

          public static int maximumSumSubSequence(int[] array) {
          
              if (null == array) {
                  return -1;
              }
          
              int maxSum = Integer.MIN_VALUE;
              int startIndexFinal = 0;
              int endIndexFinal = 0;
              int currentSum = 0;
              int startIndexCurrent = 0;
          
              for (int i = 0; i < array.length; i++) {
                  currentSum += array[i];
          
                  if (currentSum > maxSum) {
                      maxSum = currentSum;
                      endIndexFinal = i;
                      startIndexFinal = startIndexCurrent;
                  }
                  if (currentSum <= 0) {
                      currentSum = 0;
                      startIndexCurrent = i + 1;
                  }
              }
              System.out.println("startIndex: " + startIndexFinal + " endIndex: " + endIndexFinal);
              return maxSum;
          }
          

          【讨论】:

            【解决方案9】:

            这种区别对 OP 来说可能并不重要,他们似乎只是想了解如何解决问题,但我认为值得一提:

            这里的其他解决方案涉及重复总结列表的所有子部分。我们可以通过使用动态规划来避免这些重复的总和,因为当然,如果我们已经知道从 ij 的总和,我们不需要再次将它们相加以获得从 i 到 @987654324 的总和@!

            也就是说,制作一个包含部分和的二维数组,这样partsum[i, j] == sum(lst[i:j])。类似的东西(使用字典,因为它更容易索引;numpy 数组同样容易且更有效):

            import operator
            
            def mssl(lst, return_sublist=False):
                partsum = { (0, 0): 0 }  # to correctly get empty list if all are negative
                for i in xrange(len(lst) - 1):  # or range() in python 3
                    last = partsum[i, i+1] = lst[i]
                    for j in xrange(i+1, len(lst)):
                        last = partsum[i, j+1] = last + lst[j]
            
                if return_sublist:
                    (i, j), sum = max(partsum.iteritems(), key=operator.itemgetter(1))
                    return sum, lst[i:j]
            
                return max(partsum.itervalues())  # or viewvalues() in 2.7 / values() in 3.x
            

            这需要 O(n^2) 时间和内存,而 Lev/Inbar 的方法需要 O(n^3) 时间和 O(1) 内存(如果没有像 Inbar 的第一个代码示例那样愚蠢地实现)。

            【讨论】:

              【解决方案10】:

              这个post 介绍了三种查找数组最大子数组的方法。

              • 蛮力 (O(n*n))
              • 分而治之 (O(nlgn))
              • Kadane 算法 (O(n))

              其中最快的是Kadane算法,时间复杂度为O(n)。

              【讨论】:

                【解决方案11】:

                如果有人在寻找更长版本的代码,这里是:

                def mesl(lst):
                    sub_sum = list()
                    row_sum = list()
                    for i in range(len(lst)):
                        sub_sum = list()
                        sub_sum.append(lst[i])
                        k = 1
                        for j in range(i+1,len(lst)):
                            sub_sum.append(sub_sum[k-1] + lst[j])
                            k+=1
                        row_sum.append(max(sub_sum))      
                    sum = max(row_sum)
                    if  sum < 0:
                        sum = 0
                    return sum
                

                【讨论】:

                  【解决方案12】:

                  我正在介绍一种基于动态编程的方法。主要思想是,当我们遍历数组时,向我们当前的 sum-value 添加一个新元素应该增加 sum 的值,或者我们继续使用当前元素并忘记旧的 sum-value。

                  为了适应具有负值的数组,我们使用数组的第一个元素来实例化我们的变量。

                  def maxSumSubArr(arr):
                      cur_sum = best_sum = arr[0]
                      for i in range(1, len(arr)):
                          cur_sum = max(arr[i], cur_sum+arr[i])
                          best_sum = max(best_sum, cur_sum)
                      return best_sum
                  

                  这种方法的运行时间是O(n),空间复杂度是O(1)。

                  如果您希望在没有元素为正的情况下输出为零,则使用 0 实例化 cur_sum 和 best_sum 变量,并从第一个元素而不是第二个元素进行迭代。

                  【讨论】:

                    【解决方案13】:

                    这里是 javascript 中最大子数组问题的最短和最好的解决方案:

                    var maxSubArray = function(nums) {
                        for (let i = 1; i < nums.length; i++){
                            nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
                        }
                        return Math.max(...nums);
                    };
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2013-02-10
                      • 2018-03-18
                      • 2019-08-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2022-01-10
                      • 2013-02-10
                      相关资源
                      最近更新 更多