Хитрая задача из BigTech

В этом уроке ты
  • Решишь интересную задачу из собеседования в крупной IT-компании.
  • Ты поймёшь, как порой первая мысль с привычным паттерном может ввести в заблуждение и привести к не оптимальному решению.
Ты на собеседовании

Представь, что ты на собеседовании и у тебя есть 20 минут, чтобы придумать решение задачи!

Дан массив nums. Нужно найти индекс такого элемента, что сумма всех элементов слева от него равна сумме всех элементов справа.

Особые случаи:

  • Если индекс указывает на первый элемент, сумма слева считается равной 0.
  • Если индекс указывает на последний элемент, сумма справа равна 0.
  • Если такого индекса нет, вернуть 1.

Пример 1

Ввод: nums = [7, -1, 4, 3, 5, 5]
Вывод: 3
Объяснение: 7 + (-1) + 4 = 5 + 5

Пример 2

Ввод: nums = [0, 0, 0]
Вывод: 0
Объяснение: Ответом может быть 0, 1 или 2, но возвращаем наименьший индекс.
В чем сложность задачи?

Одно из очевидных решений — использовать префиксные и суффиксные суммы.

  • Префиксный массив: каждый элемент равен сумме всех предыдущих элементов исходного массива.
  • Суффиксный массив: каждый элемент равен сумме всех последующих элементов исходного массива.

Например, если nums = [7, -1, 4, 3, 5, 5], то:

  • Префиксный массив px = [0, 7, 6, 10, 13, 18, 23]
  • Суффиксный массив sx = [23, 16, 17, 13, 10, 5, 0]

Чтобы найти нужный индекс, сравниваем элементы: px[i + 1] == sx[i].

using System;
using System.Collections.Generic;

public class Solution
{
    // Время: O(n), где n - длина массива nums
    // Память: O(n)
    public static int PivotIndex(List<int> nums)
    {
        // Строим префиксный массив
        int[] px = new int[nums.Count + 1];
        for (int i = 0; i < nums.Count; i++)
        {
            px[i + 1] = px[i] + nums[i];
        }

        // Строим суффиксный массив
        int[] sx = new int[nums.Count + 1];
        for (int i = nums.Count - 1; i >= 0; i--)
        {
            sx[i] = sx[i + 1] + nums[i];
        }

        // Ищем ответ
        for (int i = 0; i < nums.Count; i++)
        {
            if (px[i + 1] == sx[i])
            {
                return i;
            }
        }

        return -1; // Если не найден индекс
    }
}

Недостаток такого подхода, что он использует дополнительную память для хранения массивов префиксных и суффиксных сумм, что не является оптимальным!

Оптимальное решение

Задачу можно решить за O(n) времени и O(1) памяти.

using System;
using System.Collections.Generic;

public class Solution
{
    // Время: O(n)
    // Память: O(1)
    public static int PivotIndex(List<int> nums)
    {
        int totalSum = 0;
        foreach (int num in nums)
        {
            totalSum += num;
        }

        int leftSum = 0;
        for (int i = 0; i < nums.Count; i++)
        {
            int rightSum = totalSum - leftSum - nums[i];
            if (leftSum == rightSum)
            {
                return i; 
            }
            leftSum += nums[i];
        }
        return -1; // Если не найден индекс
    }
}

Идея:

  1. Сначала вычисляем общую сумму элементов массива: total_sum.
  2. Затем, проходя по массиву, накапливаем сумму элементов слева: left_sum.
  3. Сумму справа вычисляем как: right_sum=total_sum−left_sum−nums[i].
  4. Если в какой-то момент выполняется условие left_sum==right_sum возвращаем текущий индекс.

Этот подход избавляет от необходимости создавать дополнительные массивы и работает оптимально. Именно такое решение чаще всего ожидает интервьюер.

Что дальше

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