具有整数的数组的最大子数组

时间:2011-10-30 08:15:30

标签: algorithm

在一次采访中,我的一位朋友被要求找到一个最大数量的数组的子阵列,这是我对问题的解决方案,我如何改进解决方案使其更加优化,我应该考虑以递归方式进行?

def get_max_sum_subset(x):
        max_subset_sum = 0
        max_subset_i = 0
        max_subset_j = 0
        for i in range(0,len(x)+1):
            for j in range(i+1,len(x)+1):
                current_sum = sum(x[i:j])
                if current_sum > max_subset_sum:
                    max_subset_sum = current_sum
                    max_subset_i = i
                    max_subset_j = j
        return max_subset_sum,max_subset_i,max_subset_j

13 个答案:

答案 0 :(得分:24)

您的解决方案是O(n ^ 2)。最佳解决方案是线性的。它的工作原理是您从左到右扫描数组,记下最佳总和和当前总和:

def get_max_sum_subset(x):
    bestSoFar = 0
    bestNow = 0
    bestStartIndexSoFar = -1
    bestStopIndexSoFar = -1
    bestStartIndexNow = -1
    for i in xrange(len(x)):
        value = bestNow + x[i]
        if value > 0:
            if bestNow == 0:
                bestStartIndexNow = i
            bestNow = value
        else:
            bestNow = 0

        if bestNow > bestSoFar:
            bestSoFar = bestNow
            bestStopIndexSoFar = i
            bestStartIndexSoFar = bestStartIndexNow

    return bestSoFar, bestStartIndexSoFar, bestStopIndexSoFar

Programming Pearls: Algorithm Design Techniques(强烈推荐)中也讨论了这个问题。在那里你也可以找到一个递归解决方案,它不是最优的(O(n log n)),但优于O(n ^ 2)。

答案 1 :(得分:18)

这是一个众所周知的问题,它显示重叠的最佳子结构,这表明了动态编程(DP)解决方案。虽然DP解决方案通常非常棘手(至少我认为如此!),但这个解决方案是一个很好的例子,可以介绍整个概念。

首先要注意的是,在位置j结束的最大子阵列(必须是给定数组A的连续部分)或者由在位置j-1结束的最大子阵列加上A [j]组成,或者是空(这仅在A [j] <0)时出现。换句话说,我们要问的是元素A [j]是否对当前在位置j-1结束的最大和有贡献。如果是,请将其包含在最大子阵列中;如果没有,不要。因此,通过解决重叠的较小子问题,我们可以建立一个最优的解决方案。

然后可以通过以下关系递归地给出在位置j结束的最大子阵列的总和:

sum[0] = max(0, A[0])
sum[j] = max(0, sum[j-1] + A[j])

我们可以通过从左到右扫描A,以自下而上的方式建立这些答案。我们考虑A [j]时更新sum [j]。我们也可以通过这个过程跟踪最大子阵列的总体最大值和位置。这是我在Ruby中写的一个快速解决方案:

def max_subarray(a)
    sum = [0]
    max, head, tail = sum[0], -1, -1
    cur_head = 0

    (0...a.size).each do |j|
        # base case included below since sum[-1] = sum[0]
        sum[j] = [0, sum[j-1] + a[j]].max
        cur_head = j if sum[j-1] == 0
        if sum[j] > max
            max, head, tail = sum[j], cur_head, j
        end
    end

    return max, head, tail
end

如果您想亲自测试一下,请查看我的gist

这显然是线性O(N)算法,因为只需要通过列表一次。希望这有帮助!

答案 2 :(得分:5)

通过考虑最大和子数组必须包含的条件,可以得出更好的解决方法:任何一端的第一项(未包括)(如果有的话)必须为负数,最后一项必须为包含的结尾必须是非负的。除了原始数据中发生这些更改之外,您不需要考虑子数组的任何其他端点。

答案 3 :(得分:4)

n - 元素数,a(i) - 数组f(i) - 在位置i结束的子数组的最大总和(最小长度为1)。然后:

f(0) = a(i);
f(i) = max(f(i-1), 0) + a(i); //f(i-1) when we continue subarray, or 0 - when start at i position

max(0, f(1), f(2), ... , f(n-1)) - 答案

答案 4 :(得分:4)

麻省理工学院有一段简短的视频可以帮助您理解这个动态编程问题。 http://people.csail.mit.edu/bdean/6.046/dp/ 点击“问题”部分下的第一个链接,您将看到它。

答案 5 :(得分:2)

这是来自http://en.wikipedia.org/wiki/Maximum_subarray_problem

的简单O(N)算法
int maxsofar=0;
int maxendinghere=0;
for i=[0 n] {
    maxendinghere=max(maxendinghere+x[i],0);
    maxsofar=max(maxsofar,maxendinghere);
}

答案 6 :(得分:1)

除非我遗漏一些重要的东西,否则如果它们是正整数,则子集将包括整个数组,如果它们是整数,则它将仅包括正整数。那里有另一个限制吗?

答案 7 :(得分:0)

Java解决方案:

对于具有所有底片的数组不起作用。

public static int[] maxsubarray(int[] array) {

    //empty array check

    if (array.length == 0){
        return new int[]{}; 
    }

    int max = 0;
    int maxsofar = 0;

    //indices 

    int maxsofarstart = 0;
    int maxsofarend = 0;
    int maxstartindex = 0;

    for (int i = 0; i < array.length; i++) {
        if (array[i] > 0) {

            if (max == 0) {
                maxstartindex = i;
            }

            max = max + array[i];

            if (max > maxsofar) {

                maxsofar = max;
                maxsofarstart = maxstartindex;
                maxsofarend = i;

            }

    } else {
        max = 0;
    }

}

return Arrays.copyOfRange(array, maxsofarstart, maxsofarend + 1);

}

答案 8 :(得分:0)

这是解释最充分,经过测试的工作解决方案之一 - http://rerun.me/blog/2012/08/30/maximum-continuous-subarray-problem-kandanes-algorithm/

package me.rerun;

public class Kadane {

    public static void main(String[] args) {
        int[] intArr={3, -1, -1, -1, -1, -1, 2, 0, 0, 0 };
        //int[] intArr = {-1, 3, -5, 4, 6, -1, 2, -7, 13, -3};
        //int[] intArr={-6,-2,-3,-4,-1,-5,-5};
        findMaxSubArray(intArr);
    }



public static void findMaxSubArray(int[] inputArray){

    int maxStartIndex=0;
    int maxEndIndex=0;
    int maxSum = Integer.MIN_VALUE; 

    int cumulativeSum= 0;
    int maxStartIndexUntilNow=0;

    for (int currentIndex = 0; currentIndex < inputArray.length; currentIndex++) {

        int eachArrayItem = inputArray[currentIndex];

        cumulativeSum+=eachArrayItem;

        if(cumulativeSum>maxSum){
            maxSum = cumulativeSum;
            maxStartIndex=maxStartIndexUntilNow;
            maxEndIndex = currentIndex;
        }
        else if (cumulativeSum<0){
            maxStartIndexUntilNow=currentIndex+1;
            cumulativeSum=0;
        }
    }

    System.out.println("Max sum         : "+maxSum);
    System.out.println("Max start index : "+maxStartIndex);
    System.out.println("Max end index   : "+maxEndIndex);
}

}

答案 9 :(得分:0)

这是正确的Java代码,它将处理包括所有负数的方案。

public static long[] leftToISumMaximize(int N, long[] D) {
    long[] result = new long[N];
    result[0] = D[0];
    long currMax = D[0];
    for (int i = 1; i < N; i++) {
        currMax = Math.max(D[i], currMax + D[i]);
        result[i] = Math.max(result[i - 1], currMax);
    }
    return result;
}

答案 10 :(得分:0)

对于在O(N)中运行的最大子阵列问题,存在一种简单的动态编程算法:Kadane's algorithm。对基本算法的轻微修改将允许您处理所有负数:是否应返回最大数字或0。

答案 11 :(得分:0)

我已经为一个更普遍的问题做了一个函数:

  • 查找最大和子数组(表示其边界和总和,而不仅仅是总和)
  • 如果两个子阵列具有相等的总和,则选择较短的一个
  • 如果两个等长的子阵列具有相等的总和,则选择首先出现的子阵列。

功能基于Kadane算法,并在O(n)时间内运行。基本上就是这样:

function MaxSumSubarray(a, n, start out, len out)
    -- a - Array
    -- n - Length of the array
    -- start - On output starting position of largest subarray
    -- len - On output length of largest subarray
    -- Returns sum of the largest subarray
begin

    start = 0
    len = 1
    int sum = a[0]

    curStart = 0
    curLen = 1
    curSum = a[0]

    for i = 2 to n
        begin
            if a[i] >= curSum + a[i] then
                begin
                    curStart = i
                    curLen = 1
                    curSum = a[i]
                end
            else
                begin
                    curLen = curLen + 1
                    curSum = curSum + a[i]
                end

            if (curSum > sum) OR
               (curSum = sum AND curLen < len) OR
               (curSum = sum AND curLen = len AND curStart < start) then
               begin
                    start = curStart
                    len = curLen
                    sum = curSum
                end

        end

    return sum

end

我已在C#中上传了整个解决方案,其中包含分析和示例:Maximum Sum Subarray

答案 12 :(得分:0)

不确定,但接受的解决方案并不适用于所有场景(可能是我误解了) 所以我做了一些小修改,而不是     if(值> 0) 我改变了哟     if(value&gt; bestNow)

.....(I wrote it in Scala)

它适用于所有场景

def findMaxSubArray(list: List[Int]): (Int, Int, Int) = {

 var (bestNow,bestSoFar) = (0, 0)
 var ( startIndexNow, startIndexSoFar, endIndex) = (-1, -1, -1)

 for (i <- 0 until list.length) {
   var value = bestNow + list(i)
   if (value > bestNow) {
     if (bestNow == 0)
       startIndexNow = i
     bestNow = value
   } else
     bestNow = 0

  if (bestNow > bestSoFar) {
    bestSoFar = bestNow
    startIndexSoFar = startIndexNow
    endIndex = i
    } 
  }

  return (bestSoFar, startIndexSoFar, endIndex)
 }     

 def main(args: Array[String]) {
  println(findMaxSubArray(List(3, -1, 5, 3, -6, -9, 6, 1)).toString)
  println(findMaxSubArray(List(3, -1, 5, 3, -6, -9, 6, 3)).toString)
  println(findMaxSubArray(List(20, -1, 5, 3, -6, -9, 6)).toString)
}

   Output.....
    (max =8, start=2, end=3)
    (max=9, start=6, end=7)
    (max=20, start=0, end= 0)