Префиксные суммы
Индекс равновесия
Найти индекс, где сумма слева равна сумме справа
Постановка задачи
Описание (LeetCode)
Дан массив целых чисел nums. Нужно найти индекс равновесия массива.
Индекс равновесия - это такой индекс i, где сумма всех элементов строго слева
от i равна сумме всех элементов строго справа от i.
Если индекс находится на левом краю массива, то сумма слева считается равной
0, потому что слева нет элементов. То же самое работает для правого края.
Если таких индексов несколько, нужно вернуть самый левый. Если подходящего
индекса нет, возвращаем -1.
Пример 1:
nums = [1, 7, 3, 6, 5, 6]
Результат:
3
Пояснение:
Сумма слева от индекса 3: 1 + 7 + 3 = 11.
Сумма справа от индекса 3: 5 + 6 = 11.
Пример 2:
nums = [1, 2, 3]
Результат:
-1
Пояснение:
Нет индекса, для которого суммы слева и справа равны.
Пример 3:
nums = [2, 1, -1]
Результат:
0
Пояснение:
Сумма слева от индекса 0 равна 0, а сумма справа равна 1 + (-1) = 0.
Ограничения:
1 <= nums.length <= 10⁴-1000 <= nums[i] <= 1000
Решение
Эта задача хорошо показывает, зачем нужны префиксные суммы. Для каждого индекса нам нужно знать две величины:
- сумму всех элементов слева;
- сумму всех элементов справа.
Наивное решение для каждого индекса заново считает сумму слева и сумму справа.
Такой подход легко реализовать, но он делает много лишней работы. Для массива
длины n мы будем пересчитывать похожие суммы снова и снова, поэтому получим
сложность O(n²).
Нам нужно заметить, что при движении слева направо сумма слева меняется очень
просто: после проверки текущего индекса мы добавляем в нее nums[i].
Идея решения:
Сначала посчитаем общую сумму массива total. Потом будем идти по массиву и
поддерживать переменную leftSum - сумму элементов слева от текущего индекса.
Если мы знаем total, leftSum и текущий элемент nums[i], то сумму справа
можно получить без отдельного прохода:
rightSum = total - leftSum - nums[i];Теперь на каждом индексе достаточно проверить leftSum === rightSum. Если
равенство выполняется, возвращаем текущий индекс. Так как мы идем слева направо,
первый найденный индекс автоматически будет самым левым.
Основные шаги:
-
Считаем
total- сумму всех элементов массива. -
Создаем переменную
leftSum = 0. -
Идем по массиву слева направо:
- считаем
rightSum = total - leftSum - nums[i]; - если
leftSum == rightSum, возвращаемi; - иначе добавляем текущий элемент к
leftSum.
- считаем
-
Если прошли весь массив и ничего не нашли, возвращаем
-1.
Рассмотрим это на примере:
nums = [1, 7, 3, 6, 5, 6]
Общая сумма массива:
total = 28
Идем по индексам:
-
Индекс
0, число1.leftSum = 0rightSum = 28 - 0 - 1 = 27- суммы не равны, добавляем
1кleftSum
-
Индекс
1, число7.leftSum = 1rightSum = 28 - 1 - 7 = 20- суммы не равны, добавляем
7кleftSum
-
Индекс
2, число3.leftSum = 8rightSum = 28 - 8 - 3 = 17- суммы не равны, добавляем
3кleftSum
-
Индекс
3, число6.leftSum = 11rightSum = 28 - 11 - 6 = 11- суммы равны, возвращаем
3
Тут важно не добавлять текущий элемент к leftSum до проверки. По условию
индекс равновесия сравнивает элементы строго слева и строго справа, а сам
nums[i] не входит ни в одну из этих сумм.
Можно построить отдельный массив префиксных сумм и через него проверять каждый
индекс. Но для этой задачи это лишнее использование памяти. Нам достаточно одной
переменной leftSum.
Оценка сложности
Временная сложность
Мы один раз считаем общую сумму массива и один раз проходим по массиву в поиске
индекса равновесия. Получаем O(n).
Пространственная сложность
Мы используем только несколько переменных: total, leftSum и rightSum.
Дополнительная память не зависит от размера массива, поэтому пространственная
сложность равна O(1).
Код решения
Приведем код решения.
func pivotIndex(nums []int) int {
total := 0
for _, num := range nums {
total += num
}
leftSum := 0
for i, num := range nums {
rightSum := total - leftSum - num
if leftSum == rightSum {
return i
}
leftSum += num
}
return -1
}Итоги
Задача учит не пересчитывать суммы с нуля.
-
Ключевая идея. Если известна общая сумма массива, сумму справа можно получить через
total - leftSum - nums[i]. -
Порядок действий. Сначала сравниваем
leftSumиrightSum, и только потом добавляем текущий элемент кleftSum. -
Почему возвращаем первый найденный индекс. Мы идем слева направо, поэтому первый подходящий индекс будет самым левым.
-
Оптимизация памяти. Для этой задачи не нужен отдельный массив префиксных сумм, достаточно одной переменной.