Найти максимальную сумму интервала в списке действительных чисел - PullRequest
6 голосов
/ 16 марта 2011

Вот вопросы интервью, которые коллега задал на должность программиста. Я думал, что это было здорово наблюдать за тем, как собеседник обдумывает это. Я хотел бы получить ответы о том, как об этом думает сообщество SO.

Учитывая список действительных чисел длины N, скажем [a_1, a_2, ..., a_N], какова сложность нахождения максимального значения M, для которого существуют индексы 1 <= i <= j <= N, такие что </p>

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

Приношу свои извинения, если это классическая проблема CS.

Ответы [ 8 ]

8 голосов
/ 16 марта 2011

Сложность просто O (n) для алгоритма Кадане :

Алгоритм отслеживает предварительную максимальную подпоследовательность в (maxSum, maxStartIndex, maxEndIndex). Он накапливает частичную сумму в currentMaxSum и обновляет оптимальный диапазон, когда эта частичная сумма становится больше, чем maxSum.

7 голосов
/ 16 марта 2011

Это 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;
}

Идея состоит в том, чтобы сохранить сумму всех целых чисел, которые встречались с момента последнего сброса.Сброс происходит, когда сумма опускается ниже нуля, т. Е. В текущем интервале слишком много отрицательных чисел, чтобы сделать его, возможно, лучшим.

3 голосов
/ 12 марта 2013

Это классическая, хорошо известная проблема, которая отлично открывает глаза в любом курсе алгоритмов.Трудно найти лучший / более простой стартер.Вы можете найти n * 3-, n * 2-, nlogn- и даже простой n-алгоритм.

Я нашел проблему, которая обсуждалась / решалась в «Программировании жемчуга» Джона Бентли в 1986 году, и в течение многих лет использовал ее в качестве стартового курса в нашем Алгоритмическом курсе в NTNU / Тронхейме.Около 20 лет назад я впервые использовал его на экзамене для 250 студентов, где только 1 студент обнаружил все 4 решения, см. Выше.Он, Бьёрн Олстад, стал «самым молодым профессором» в NTNU в Тронхейме и до сих пор имеет этот статус, кроме того, что он возглавляет поисковое подразделение MSFT в Осло.Бьёрн также взял на себя задачу найти хорошее практическое применение алгоритма.Вы видите?

  • Арне Халаас
2 голосов
/ 31 августа 2012

Попробуйте этот код .. он будет работать нормально, по крайней мере, для одного + 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++]);
    }
}
1 голос
/ 25 января 2014

Я вторгаюсь в эту древнюю ветку, чтобы дать подробное объяснение того, почему алгоритм Кадане работает. Алгоритм был представлен в классе, в котором я сейчас учусь, но только с неопределенным объяснением. Вот реализация алгоритма в 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 l - наибольшая сумма начального сегмента l, а maxCont - максимальная непрерывная сумма l.

Тривиальный, но важный факт: для всех l, maxInit l ≤ maxCont l. Должно быть очевидно, что приведенная выше спецификация гарантирует maxCont l = maxCont' 0 0 l, чего мы и хотим. Вместо того, чтобы пытаться объяснить, почему окончательная версия maxCont 'реализует приведенную выше спецификацию (которую я не знаю, как это сделать), я покажу, как ее можно извлечь из нее, преобразовывая спецификацию по одному шагу за раз, пока это становится кодом, который тогда, безусловно, будет правильным. Как написано, эта спецификация не дает реализацию: если maxCont определен в терминах maxCont', как описано выше, он будет зацикливаться вечно, поскольку maxCont' вызывает maxCont, вызывает maxCont' с тем же списком. Итак, давайте немного расширим его, чтобы показать фрагменты, которые нам понадобятся:

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

Это еще ничего не исправило, но разоблачило вещи. Давайте использовать это. thisSum + maxInit (x:xs) это либо thisSum, либо thisSum + 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]

Этот, наконец, правильно рекурсивный, но у нас есть способы добраться до эффективного кода, особенно потому, что мифический maxInit будет слишком медленным. Давайте разберем его на три случая, рассмотренные в коде Java (немного злоупотребляя нотацией на Haskell):

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]

В первом случае мы знаем, что 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 голос
/ 16 марта 2011

Это может быть неправильно, потому что это подозрительно просто.

  1. Начните суммировать все элементы от 0 до n и определите индекс, где сумма прокрутки была наибольшей.Это будет верхняя граница вашего интервала.
  2. Сделайте то же самое в обратном направлении, чтобы получить нижнюю границу.(Достаточно, если вы начнете с верхней границы.)

Это похоже на O (n).

0 голосов
/ 13 апреля 2019

Я бы добавил ответ, содержащий 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. </li>
  • findIncludeNonPositive()
    Найти максимальный подмассив, также включить подмассив с неположительной суммой.
    Поведение:

    • Для массива, содержащего только неположительные элементы, будет выбран первый наименьший элемент.
    • Для пустого входного массива, выбор пустого подмассива начинается с 0.

    Кстати:

    • Перезапускается всякий раз, когда предыдущая сумма <= 0. </li>
    • Предпочитается запуск подмассива с меньшего индекса, когда существует несколько максимальных подмассивов.
    • Он не будет содержать ненужных 0 в конце подмассива, что не меняет сумму.

Сложность:

  • Время: O(n)
  • Пробел: O(1)
0 голосов
/ 16 февраля 2015

Я пробовал и проверял это. Если все числа отрицательные, возвращается наибольшее отрицательное число.

Контрольные примеры:

{-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;
}
...