Рекурсия: вырезать массив целых чисел в двух частях равной суммы - за один проход

Используя рекурсию, найдите индекс, который разрезает массив в две части, чтобы обе части имели равную сумму.

Вырезать означает вырезать как нож. Все ячейки с индексом <= к результату должны быть равны по своей сумме ко всем ячейкам с индексом > к результату. Никакие клетки не могут быть оставлены или быть частью обеих сторон.

Массивы содержат произвольные целые числа (т.е. положительные, отрицательные и нулевые значения).

Если такой индекс не возвращается -1.

Вам не разрешено выделять объекты кучи.

Вы должны сделать это за один проход.

Вы должны сделать это с помощью рекурсии (т.е. не использовать конструкции цикла).

Может быть на любом языке или псевдокоде.

Забыл добавить это: вы не можете изменить массив

Ответы

Ответ 1

Вот способ сделать это, который использует возможности Ruby для возврата нескольких значений. Первое значение - это индекс для разделения (если он существует), второй - сумма каждой половины (или сумма целого массива, если раскол не найден):

def split(arr, index = 0, sum = 0)
  return -1, arr[index] if index == arr.length - 1
  sum = sum + arr[index]
  i, tail = split(arr, index + 1, sum)

  if i > -1
    return i, tail
  elsif sum == tail
    return index, sum 
  end
  return -1, arr[index] + tail
end

Вызвать это следующим образом:

p split([1, 1, 2])
p split([1])
p split([-1, 2, 1])
p split([2, 3, 4])
p split([0, 5, 4, -9])

Результаты в этом:

[1, 2]
[-1, 1]
[1, 1]
[-1, 9]
[0, 0]

EDIT:


Здесь немного измененная версия для обращения к комментариям onebyone.livejournal.com. Теперь каждый индекс в массиве обращается только один раз:

def split(arr, index = 0, sum = 0)
  curr = arr[index]
  return -1, curr if index == arr.length - 1
  sum = sum + curr
  i, tail = split(arr, index + 1, sum)

  if i > -1
    return i, tail
  elsif sum == tail
    return index, sum 
  end
  return -1, curr + tail
end

Ответ 2

Итерация с рекурсией является тривиальным преобразованием, поэтому предположим, что вы знаете, как это сделать.

Если вы используете свой "один проход" для создания собственного массива "sum to this index" и можете сделать еще один проход по этому массиву, я мог бы увидеть, как это сделать. Просто перебираем этот второй массив и вычитаем сумму [x] из sum [last]. Если вы когда-нибудь найдете ситуацию, когда результат = sum [x], вы возвращаете x. Если вы этого не сделаете, верните -1. ​​

Как упоминал Neil N, если вы решите "пройти" очень свободно для рекурсии, так что вся рекурсия может на самом деле посещать индексы несколько раз, тогда вы можете обойтись без второго массива.


Подумав об этом немного, я подозреваю, что идея состоит в том, чтобы заставить вас сразу посещать каждый элемент массива один раз (по порядку) и использовать встроенное свойство стека рекурсии, чтобы избавиться от необходимости в любом втором массиве.

Что вы делаете, это написать свою рекурсивную подпрограмму, чтобы сэкономить значение текущего значения индекса в локальном, добавить это значение в переданное значение "sum_of_array", а затем вызвать себя на следующем самом высоком индексе (если таковой имеется). Если нет более высокого индекса, он сохраняет сумму в глобальную, которая теперь доступна для каждого сложного рекурсивного вызова. Каждая процедура заканчивается, проверяя ее сумму против глобальной суммы. Если он равен половине, то он возвращает свой индекс. В противном случае он возвращает -1. Если не--1 был возвращен из вызова самому себе, этот последний шаг пропускается и возвращается это значение. Я покажу в псевдо-Аде

Total_Sum : integer;

function Split (Subject : Integer_Array; After : Integer := 0; Running_Sum : Integer := 0) is
begin
   Running_Sum := Running_Sum + Subject(After);
   if (After < Subject'last) then --'// comment Hack for SO colorizer
      Magic_Index : constant Integer := Split (Subject, After +  1, Running_Sum);
      if (Magic_Index = -1) then
         if (Total_Sum - Running_Sum = Running_Sum) then
            return After;
         else
            return -1;
         end if;
      else
         return Magic_Index;
      end if;
   else
      Total_Sum := Running_Sum;
      return -1;
   end if;
end Split;

Этот код должен иметь следующие свойства:

  • Вызов с помощью только массива возвращает описанный "split" index или -1, если его нет.
  • Он только читает из любого элемента в исходном массиве один раз
  • Он считывает исходные элементы массива в строгом порядке индекса.
  • Не требуется дополнительное структурированное хранилище данных (массив).

Ответ 3

public static Int32 SplitIndex(Int32[] array, Int32 left, Int32 right, Int32 leftsum, Int32 rightsum)
{
    if (left == right - 1)
    {
        return (leftsum == rightsum) ? left : -1;
    }

    if (leftsum > rightsum)
    {
        return SplitIndex(array, left, right - 1, leftsum, rightsum + array[right - 1]);
    }
    else
    {
        return SplitIndex(array, left + 1, right, leftsum + array[left + 1], rightsum);
    }
}

Метод вызывается следующим образом.

Int32[] a = { 1, 2, 3, 1, 6, 1 };

Console.WriteLine(SplitIndex(a, -1, a.Length, 0, 0));

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

public static Int32 SplitIndex(Int32[] array, Int32 left, Int32 right, Int32 sum)
{
    if (left == right - 1)
    {
        return (sum == 0) ? left : -1;
    }

    if (sum > 0)
    {
        return SplitIndex(array, left, right - 1, sum - array[right - 1]);
    }
    else
    {
        return SplitIndex(array, left + 1, right, sum + array[left + 1]);
    }
}

Теперь метод называется следующим.

Int32[] a = { 1, 2, 3, 1, 6, 1 };

Console.WriteLine(SplitIndex(a, -1, a.Length, 0));

Ответ 4

Посмотрите на следующее, используя только 1 индекс, предположим, что индексы массива основаны на 1:

int recursion(index, rightvalue, leftvalue, array)
{
if array=[] then
{
    if rightvalue=leftvalue then return index
    else return -1
}
else
{
    if rightvalue <= leftvalue
    { recursion(index+1, rightvalue+array[1], leftvalue, array[2..len(array)] }
    else 
    { recursion(index, rightvalue, leftvalue+array[len(array)], array[1..len(array)-1] }
}

int main_function(array)
{
    return recursion(1, 0, 0, array)
}

Ответ 5

Моя версия:

# Returns either (right sum from the currentIndex, currentIndex, False),
# or, if the winning cut is found, (sum from the cut, its index, True)
def tryCut(anArray, currentIndex, currentLeftSum):
   if currentIndex == len(anArray):
      return (0, currentIndex, currentLeftSum==0)

   (nextRightSum, anIndex, isItTheWinner) = tryCut(anArray, currentIndex + 1, currentLeftSum + anArray[currentIndex])

   if isItTheWinner: return (nextRightSum, anIndex, isItTheWinner)
   rightSum = anArray[currentIndex] + nextRightSum
   return (rightSum, currentIndex, currentLeftSum == rightSum)

def findCut(anArray):
   (dummy, anIndex, isItTheWinner) = tryCut(anArray, 0, 0)
   if isItTheWinner: return anIndex
   return -1

Примечание: если возвращаемый индекс равен 5, я имею в виду, что sum (anArray [: 5]) == sum (anArray [5:]). "Крайности" также действительны (где сумма пустого среза означает нуль), т.е. Если сумма всего массива равна нулю, то 0 и len (anArray) также являются допустимыми сокращениями.

Ответ 6

Здесь реализация в Erlang, так как я изучаю ее, и это казалось интересной задачей. Идея бесстыдно скрещена с решением Песто.

find_split(List) -> {Idx, _Sum} = find_split(List, 1, 0), Idx.
find_split([Head], _Idx, _Sum) -> {-1, Head};
find_split([Head|Tail], Idx, Sum) ->
  case find_split(Tail, Idx + 1, Sum + Head) of
    {-1, Tailsum} when Sum + Head == Tailsum -> {Idx, Sum + Head};
    {-1, Tailsum} -> {-1, Head + Tailsum};
    Ret -> Ret
  end.

Ответ 7

Haskell:

split' _ s [] = (-1, s)
split' idx s (x:xs) | sidx >= 0   = (sidx, s')
                    | s * 2 == s' = (idx - 1, s)
                    | otherwise   = (-1, s')
    where (sidx, s') = split' (idx + 1) (x + s) xs

split = fst . split' 0 0

Ваши правила несколько вводят в заблуждение. Вам нужно, чтобы в куче не было выделено никаких объектов, но IMHO не существует решения, в котором алгоритм не имеет требований к пространству O(n), т.е. стек растет линейно с длиной списка, а хвостовые вызовы невозможны, потому что функция должна проверять возвращаемые значения из рекурсивного вызова.

Ответ 8

Код в C/С++/Java:

function cut(int i, int j, int s1, int s2, int a[])
{
    if(i==j && s1==s2)
        return i;
    else if(i==j && s1!=s2)
        return -1;
    else if(s1>s2)
        return cut(i, j-1, s1, s2 + a[j-1]);
    else
        return cut(i+1, j, s1 + a[i+1], s2);
}

Вызов с использованием следующего синтаксиса:

cut(0, array.length, 0, 0, array);