在实数列表中查找最大间隔总和

时间:2011-03-16 19:58:10

标签: algorithm math complexity-theory

以下是一位同事要求编程职位的面试问题。我认为这对于观看受访者的思考非常有用。我很想得到SO社区如何看待它的回应。

给定长度为N的实数列表,比如[a_1, a_2, ..., a_N],找到存在索引1< = i< = j< = N的最大值M的复杂度是多少

a_i + a_{i+1} + ... + a_j = M

如果这是典型的CS问题我很抱歉。

9 个答案:

答案 0 :(得分:8)

复杂性为just O(n) for Kadane's algorithm

  

该算法跟踪(maxSum, maxStartIndex, maxEndIndex)中的暂定最大子序列。它会累积currentMaxSum中的部分和,并在此部分和大于maxSum时更新最佳范围。

答案 1 :(得分:7)

这是O(N)

int sum = 0;
int M = 0;  // This is the output
foreach (int n in input)  {
  sum += n;
  if (sum > M)
      M = sum;

  if (sum < 0)
    sum = 0;
}

这个想法是保持自上次重置以来遇到的所有整数的总和。当总和低于零时发生复位 - 即当前间隔中有太多负数使其可能是最佳值。

答案 2 :(得分:3)

这是一个众所周知的经典问题,在任何算法课程中都是一个非常令人大开眼界的问题。很难找到更好/更简单的起动器。 您可以找到n * 3,n * 2-,nlogn-甚至是简单的n算法。

我发现1986年John Bentley的“编程珍珠”中讨论/解决了这个问题 - 并且在我们在NTNU / Trondheim的算法课程中使用它多年作为入门者。 大约20年前,我第一次在大约250名学生的考试中使用它,只有1名学生确实发现了所有4种解决方案,见上文。他,BjørnOlstad,在特隆赫姆的NTNU成为“有史以来最年轻的教授”,并且在奥斯陆的MSFT搜索部门领导下仍然处于这种地位。 Bjørn也接受了挑战,找到了算法的实际应用。你看到了吗?

  • Arne Halaas

答案 3 :(得分:2)

尝试这段代码..它可以在数组中至少有一个+ ve数字工作正常.. O(n)只有一个用于循环使用..

public static void main(String[] args) {
    int length ;
    int a[]={-12, 14, 0, -4, 61, -39};  
    length=a.length;

    int absoluteMax=0, localMax=0, startIndex=0, lastIndex=0, tempStartIndex=0;
    for (int index=0;index<length;index++) {
        localMax= localMax + a[index];
        if(localMax < 0){ localMax=0; tempStartIndex = index + 1;}
        if(absoluteMax < localMax) {
            absoluteMax = localMax; 
            lastIndex =index; 
            startIndex=tempStartIndex;
        }
    }

    System.out.println("startIndex  "+startIndex+"  lastIndex "+ lastIndex);    
    while (startIndex <= lastIndex) {
        System.out.print(" "+a[startIndex++]);
    }
}

答案 4 :(得分:1)

这可能是错的,因为它很可疑。

  1. 开始将从0到n的所有元素求和,并确定滚动总和最高的索引。这将是您间隔的上限。
  2. 向后做同样的事情来获得你的下边界。 (如果你从上边界开始就足够了。)
  3. 这看起来像O(n)。

答案 5 :(得分:1)

我正在介绍这个古老的线程,以详细解释为什么Kadane的算法有效。该算法是在我目前正在进行的课程中提出的,但只有一个模糊的解释。这是Haskell中算法的实现:

maxCont l = maxCont' 0 0 l

maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
  | newSum > maxSum = maxCont' newSum newSum xs
  | newSum < 0      = maxCont' maxSum 0 xs
  | otherwise       = maxCont' maxSum newsum xs
  where
    newSum = thisSum + x

现在我们只是想了解算法,让我们撤消命名newSum的次要优化:

maxCont l = maxCont' 0 0 l

maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
  | thisSum + x > maxSum = maxCont' (thisSum + x) (thisSum+x) xs
  | thisSum + x < 0      = maxCont' maxSum 0 xs
  | otherwise            = maxCont' maxSum (thisSum+x) xs

这个疯狂的功能maxCont'是什么?让我们想出一个简单的规范,说明它应该做什么。我们希望保留以下内容,前提是0 ≤ thisSum ≤ maxSum

maxCont' maxSum thisSum [] = maxSum
maxCont' maxSum thisSum l  = maximum [maxSum, thisSum + maxInit l, maxCont l]

其中maxInit llmaxCont的初始段的最大总和,是l的最大连续总和。

琐碎而重要的事实:适用于所有lmaxInit l ≤ maxCont l。很明显,上述规范保证了maxCont l = maxCont' 0 0 l,这正是我们想要的。而不是试图直接解释为什么maxCont的最终版本实现了上面的规范(我真的不知道该怎么做),我将展示如何从中导出它,一次一步地转换规范,直到它成为代码,然后肯定是正确的。如上所述,此规范未提供实现:如果maxCont如上所述按maxCont'定义,则maxCont'调用maxCont调用{maxCont'将永久循环{1}}具有相同的列表。所以让我们稍微扩展它以揭示我们需要的部分:

maxCont' maxSum thisSum (x:xs) =
                     maximum [maxSum, thisSum + maxInit (x:xs), maxCont (x:xs)]

这还没有解决任何问题,但它暴露了一些事情。让我们用它。 thisSum + maxInit (x:xs)thisSumthisSum + x + maxInit xs。但thisSum ≤ maxSum在前提条件下,所以我们可以在计算最大值时忽略这种可能性。 maxCont (x:xs)是一个包含x或不包括x的总和。但是,如果它包含maxInit (x:xs),那么它与前面所涵盖的maxCont (x:xs) = maxCont xs相同,因此我们可以忽略这种可能性,并且只考虑maxCont' maxSum thisSum (x:xs) = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs] 的情况。所以我们到达下一个版本:

maxCont' maxSum thisSum (x:xs)
  | maxSum < thisSum + x     = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
  | thisSum + x < 0          = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
  | 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]

最后,这个是正确的递归,但我们有办法去获得高效的代码,特别是因为神秘的maxInit会太慢。让我们将其分解为Java代码中考虑的三种情况(稍微滥用Haskell表示法):

maxSum

在第一种情况下,我们知道thisSum+x不能是最大值:maxInit xs更大,thisSum+x+maxInit xs总是正数。在第二种情况下,我们知道maxCont xs不能是最大值:maxInit xs始终至少与thisSum+x一样大,maxCont' maxSum thisSum (x:xs) | maxSum < thisSum + x = maximum [ thisSum+x+maxInit xs, maxCont xs] | thisSum + x < 0 = maximum [maxSum, maxCont xs] | 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs] 为负数。所以我们可以消除这些可能性:

maxCont'

现在我们几乎没有足够的优势扭转局面。现在我们已经消除了不可能的情况,我们将添加一些重复的情况,这将把这三个案例放回到相同的形式,以便我们可以替换thisSum ≤ maxSum的原始规范。在第一种情况下,我们没有第一个任期,所以我们需要使用我们知道不会超过其他条款的东西。要保持thisSum+x的不变量,我们需要使用something+maxInit xs。在第二种情况下,我们没有看起来像maxInit xs ≤ maxCont xs的第二个术语,但我们知道0+maxInit xs,因此我们可以安全地使用maxCont' maxSum thisSum (x:xs) | maxSum < thisSum + x = maximum [(thisSum+x), (thisSum+x)+maxInit xs, maxCont xs] | thisSum + x < 0 = maximum [maxSum, 0+maxInit xs, maxCont xs] | 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs] 。为规律性添加这些额外条款会产生以下结果:

maxCont' maxSum thisSum l = maximum [maxSum, thisSum + maxInit l, maxCont l]

最后,检查了前提条件后,我们在规范中替换

maxCont' maxSum thisSum (x:xs)
  | maxSum < thisSum + x     = maxCont' (thisSum+x) (thisSum+x) xs
  | thisSum + x < 0          = maxCont' maxSum 0 xs
  | 0 ≤ thisSum + x ≤ maxSum = maxCont' maxSum (thisSum+x) xs

获取

maxCont :: (Num a, Ord a) => [a] -> a
maxCont = fst . foldl maxCont' (0,0)
  where
    maxCont' (maxSum, thisSum) x
      | maxSum < newSum = (newSum, newSum)
      | newSum < 0      = (maxSum, 0)
      | otherwise       = (maxSum, newSum)
      where newSum = thisSum + x

将其修复为实际语法并在省略的基本情况下进行处理会产生实际算法,只要它终止,我们现在已经证明它已满足规范。但是每个连续的递归步骤都在较短的列表上运行,因此确实会终止。

为了我的缘故,还有最后一件事要做,就是更具惯用性和灵活性地编写最终代码:

{{1}}

答案 6 :(得分:0)

我已经尝试并测试了这个。如果所有数字都是负数,则返回最大的负数。

测试用例:

{-5, -1, -2, -3, -4}
{ 12, 14, 0, -4, 61, -39}
{2, -8, 3, -2, 4, -10}

代码:

public int FindLargestSum(int[] arr)
{
    int max = Integer.MIN_VALUE;
    int sum = 0;

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

            sum += arr[i];

            if(sum < 0)
                sum = 0;
            else if(sum > max)
                max = sum;
        }

    return max;
}

答案 7 :(得分:0)

我将添加一个包含2种方法的答案,即用Java来处理带有或不带有正元素的数组。


代码-Java

MaxSubSum.java:

public class MaxSubSum {
    /**
     * Find max sub array, only include sub array with positive sum.
     * <p>For array that only contains non-positive elements, will choose empty sub array start from 0.
     * <p>For empty input array, will choose empty sub array start from 0.
     *
     * @param arr input array,
     * @return array of length 3, with elements as: {maxSum, startIdx, len};
     * <p>tips: should use 'len' when loop the returned max sub array, so that it could also work for empty sub array,
     */
    public static int[] find(int[] arr) {
        if (arr.length == 0) return new int[]{0, 0, 0}; // empty array, no sub array,

        int maxSum = 0;    // max sum, found so far,
        int maxStart = 0;  // start of max sum,
        int maxLen = 0;    // length of max subarray,

        int sum = 0;  // current sum,
        int start = 0;    // current start,

        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > 0) { // get a positive,
                if (sum <= 0) {  // should restart,
                    start = i;
                    sum = arr[i];
                } else sum += arr[i];

                if (sum > maxSum) { // get a larger sum,
                    maxSum = sum;
                    maxStart = start;
                    maxLen = i - start + 1;
                }
            } else sum += arr[i]; // 0 or negative number,
        }

        return new int[]{maxSum, maxStart, maxLen};
    }

    /**
     * Find max sub array, also include sub array with non-positive sum.
     * <p>For array that only contains non-positive elements, will choose first smallest element.
     * <p>For empty input array, will choose empty sub array start from 0.
     *
     * @param arr input array,
     * @return array of length 3, with elements as: {maxSum, startIdx, len};
     * <p>tips: should use 'len' when loop the returned max sub array, so that it could also work for empty sub array,
     */
    public static int[] findIncludeNonPositive(int[] arr) {
        if (arr.length == 0) return new int[]{0, 0, 0}; // empty array, no sub array,

        int maxSum = arr[0];    // max sum, found so far,
        int maxStart = 0;    // start of max sum,
        int maxLen = 1;    // length of max subarray,

        int sum = arr[0];  // current sum,
        int start = 0;    // current start,

        for (int i = 1; i < arr.length; i++) {
            if (sum <= 0) { // should restart,
                start = i;
                sum = arr[i];
            } else sum += arr[i];

            if (sum > maxSum) { // get a larger sum,
                maxSum = sum;
                maxStart = start;
                maxLen = i - start + 1;
            }
        }

        return new int[]{maxSum, maxStart, maxLen};
    }
}

MaxSubSumTest.java:
(测试用例,通过TestNG

import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Arrays;

public class MaxSubSumTest {
    @Test
    public void test_find() {
        Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-2, -3, 4, -1, -2, 1, 5, -3}), new int[]{7, 2, 5})); // max sub array: {4, -1, -2, 1, 5}
        Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{12, 14, 0, -4, 61, -39}), new int[]{83, 0, 5})); // max sub array: {12, 14, 0, -4, 61}

        // corner
        Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{}), new int[]{0, 0, 0})); // empty array,

        Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-5, -4, 0, 0, -7, 0, -2}), new int[]{0, 0, 0})); // array with all elements <= 0,
        Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-5, -4, -2, -7, -2, -9}), new int[]{0, 0, 0})); // array with all elements < 0,
    }

    @Test
    public void test_findIncludeNonPositive() {
        Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-2, -3, 4, -1, -2, 1, 5, -3}), new int[]{7, 2, 5})); // max sub array: {4, -1, -2, 1, 5}
        Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{12, 14, 0, -4, 61, -39}), new int[]{83, 0, 5})); // max sub array: {12, 14, 0, -4, 61}

        // corner
        Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{}), new int[]{0, 0, 0})); // empty array,

        Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-5, -4, 0, 0, -7, 0, -2}), new int[]{0, 2, 1})); // array with all elements <= 0,
        Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-5, -4, -2, -7, -2, -9}), new int[]{-2, 2, 1})); // array with all elements < 0,
    }
}

说明:

  • find()
    查找最大子数组,仅包括具有正和的子数组。
    行为:

    • 对于仅包含非正元素的数组,将选择从0开始的空子数组。
    • 对于空输入数组,将从0开始选择空子数组。

    顺便说一句:

    • 仅当得到正数且总和<= 0时,它才会重新启动。
  • findIncludeNonPositive()
    查找最大子数组,还包括具有非正和的子数组。
    行为:

    • 对于仅包含非正元素的数组,将选择第一个最小的元素。
    • 对于空输入数组,将从0开始选择空子数组。

    顺便说一句:

    • 只要先前的总和<= 0,它就会重新启动。
    • 当有多个最大子数组时,它更喜欢从较小的索引开始的子数组。
    • 它不会在子数组末尾包含不必要的0,这不会改变总和。

复杂度:

  • 时间:O(n)
  • 空格:O(1)

答案 8 :(得分:0)

我们可以只使用最简单的两行代码算法, 这很简单,也可以处理所有否定的问题:)

curr_max = max(a [i],curr_max + a [i]);

max_so_far = max(max_so_far,curr_max;

例如C ++

int maxSubArraySum(int a[], int size) 
{ 
    int max_so_far = a[0]; 
    int curr_max = a[0]; 

    for (int i = 1; i < size; i++) 
    { 
         curr_max = max(a[i], curr_max+a[i]); 
         max_so_far = max(max_so_far, curr_max; 
    }  
    return max_so_far; 
 }