← Назад

1769.Minimum Number of Operations to Move All Balls to Each Box

Ссылка на задачу: https://leetcode.com/problems/minimum-number-of-operations-to-move-all-balls-to-each-box/description/


массивы

medium


Описание

Имеется n коробок. На вход задана бинарная строка boxes (состоит только из 0 и 1) длины n, где boxes[i] равен ‘0’ в том случае, если i-тая коробка пустая, и равен ‘1’, если i-тая коробока содержит один мяч.

За одну операцию можно переместить мяч из текущей коробки в соседнюю. Коробки i и j считаются соседними, если abs(i - j) == 1. Обратите внимание, что после этого в некоторых коробках может оказаться более одного мяча.

Необходимо вернуть массив answer размера n такой, в котором answer[i] представляет собой минимальное количество операций, необходимое для того, чтобы переместить все остальные мячи в i-тую коробку.

Каждый answer[i] рассчитывается с учетом начального состояния коробок.


Примеры

Пример 1

Input: “110”

Output: [1,1,3]

Объяснение:

  1. Нулевой индекс: нужно переместить только один мяч из корбки с индексом 1, это можно сделать всего за одну операцию (коробки соседние)

  2. Индекс 1: нужно переместить только один мяч из коробки с индексом 0, это тоже можно сделать всего за одну операцию

  3. Индекс 2: нужно переместить два мяча (с индексами 0 и 1 соответственно); для перемещения первого потребуется две операции (из 0-ой позиции в 1-ую и из 1-ой в 2-ую), а для перемещения второго - одна операция (из 1-ой позиции сразу во 2-ую); результирующее количество операций - это 2 + 1, то есть 3

Пример 2

Input: “001011”

Output: [11,8,5,4,3,4]

Объяснение:

  1. Нулевой индекс: необходимо переместить мячи с позициями 2, 4 и 5; мяч с индексом 2 может быть премещен за 2 операции, с индексом 4 - за 4 операции, с индексом 5 - за 5 операций; результат - 2 + 4 + 5, то есть 11

  2. Смысл тот же


Ограничения


Решение

Способ 1

Данный способ подразумевает решение «в лоб». Необходимо пробежаться по всему массиву boxes целиком и ответить на вопрос, какое минимальное количество операций требуется, чтобы переместить в i-тую коробку все остальные мячи. «Все остальные» означает то, что нужно дополнительно итерироваться по массиву еще раз.

Так, для текущего индекса i мы проверяем все другие индексы j того же массива. Но нас интересует только те коробки, в которых есть мяч. Пустые не представляют никакого интереса. Поэтому добавляется дополнительное условие на то, что boxes[j] != ‘0’.

Сколько операций нужно для перемещения мяча из j-ой коробки в i-тую, если за раз мы имеем право перекладывать мяч только в соседнюю коробку, то есть за операцию мы можем шагнуть вдоль массива лишь раз? Ровно abs(i-j).

Индекс i может быть как меньше j, так и больше. Поэтому используется модуль разницы.

Алгоритм:

  1. Итерируемся по всему массиву целиком (индекс i)

  2. Для текущей внешней итерации выполняем еще одну внутреннюю итерацию по массиву (индекс j)

  3. Если элемент boxes[j] равен ‘1’, то к i-тому элементу искомого ans прибавляем модуль разности i и j

Код:

python

class Solution:
    def minOperations(self, boxes: str) -> List[int]:
        ans = [0] * len(boxes)

        for i in range(len(boxes)):
            for j in range(len(boxes)):
                if boxes[j] == '1':
                    ans[i] += abs(i - j)
        return ans

C++

class Solution {
public:
    vector<int> minOperations(string boxes) {
        vector<int> ans(boxes.size());

        for (int i = 0; i < boxes.size(); ++i) {
            for (int j = 0; j < boxes.size(); ++j) {
                if (boxes[j] == '1') {
                    ans[i] += abs(i - j);
                }
            }
        }

        return ans;
    }
};

Сложность алгоритма по времени - O(N^2)

Имеем два цикла. Внешний отвечает за итерацию по массиву, внутренний - за ответ на вопрос, сколько операций требуется для текущего индекса внешнего цикла. Внутренний цикл - повторный перебор все элементов. Итак, имеем O(N*N)

Сложность алгоритма по памяти - O(1)

В данном случае не используется никаких дополнительных объектов для решения задачи. Память под сам ans в оценке не участвует, так как ans - искомый объект.


Способ 2

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

Прежде чем приступать к описанию шагов алгоритма, давайте заметим некоторый нюанс.

Допустим, что мы знаем, какое минимальное количество операций требуется для перекладки в коробку i-того индекса.

Схема

Для i-того элемента значение равно 6 (обратите внимание: нас интересуют только единицы, поэтому они отмечены зеленым цветом).

Как при этом оно поменяется, если шагнуть один раз вперед?

Схема

Сотрим на то, что слева от i+1. Чтобы попасть из i-2 в теперь уже i+1, потребовалось 3 шага (было 2), из появившегося i понадобился 1 шаг. Все старые значения, что находились левее, увеличились ровно на 1. Также появилась одна новая единица, которая тоже внесла свой вклад (+1).

Смотрим на то, что справа от i + 1. Во-первых, мы приблизились на шаг к i+3, поэтому значение для него стало 2 (было 3). Во-вторых, единица i+1, что для i стояла справа, теперь ушла (обратите внимание: на рисунке она теперь красная). Все старые значения, что находились правее, уменьшились ровно на 1. Также исчезла одна старая единица, которая тоже внесла свой вклад (-1).

Так, значение для ans[i + 1] = ans[i] + count_left - count_right. Дополнительно нужно выполнить ans[i + 1] -= 1.

Почему + count_left? Каждая единица слева от текущего элемента вносит свой вклад в размере +1. Значит суммарный вклад равен их количеству.

Почему - count_right? Каждая единица справа от текущего элемента становится ближе на 1. Суммарно их вклад уменьшается на их количество.

Зачем еще раз вычитать единицу из ans[i+1]? Сам элемент ans[i + 1] является единицей, но он не находится ни справа, ни слева, при этом его не нужно перемещать в его же коробку, он уже находится на своем месте.

Что потребовалось, чтобы определить значение ans[i+1]? Во-первых, предыдущее значение ans[i]. Во-вторых, количество единиц справа. В-третьих, количество единиц слева.

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

Алгоритм:

  1. Итерируемся по всему массиву boxes целиком, начиная с индекса 1, чтобы получить начальное значение ans[0] и посчитать количество единиц справа

  2. Иницаилизуем счетчик количества единиц слева нулем и начинаем повторную итерацию по массиву boxes с индекса 1

  3. Проверяем, если предыдущий элемент boxes был ‘1’, то инкрементируем счетчик единиц слева

  4. Проверяем, если i-тый элемент boxes равен ‘1’, то декрментируем счетчик единиц справа

  5. Определяем ans[i] как ans[i - 1] + счетчик слева - счетчик справа

  6. Дополнительно вычитаем 1 из ans[i], если i-тый элемент boxes равен ‘1’

Код:

python

class Solution:
    def minOperations(self, boxes: str) -> List[int]:
        n = len(boxes)

        ans = [0] * n

        count_ones_right = 0

        # time: O(N)
        for i in range(1, n):
            ans[0] += int(boxes[i]) * i
            if boxes[i] == '1':
                count_ones_right += 1

        count_ones_left = 0

        # time: O(N)
        for i in range(1, n):
            if boxes[i - 1] == '1':
                count_ones_left += 1

            if boxes[i] == '1':
                count_ones_right -= 1

            ans[i] = ans[i - 1] - count_ones_right + count_ones_left

            if boxes[i] == '1':
                ans[i] -= 1

        return ans

C++

class Solution {
public:
    vector<int> minOperations(string boxes) {
        const int n = boxes.size();

        vector<int> ans(n);

        int count_ones_right = 0;

        // time: O(N)
        for (int i = 1; i < n; ++i) {
            ans[0] += (boxes[i] - '0') * i;
            if (boxes[i] == '1') {
                count_ones_right += 1;
            }
        }

        int count_ones_left = 0;

        // time: O(N)
        for (int i = 1; i < n; ++i) {
            if (boxes[i - 1] == '1') {
                count_ones_left += 1;
            }
            if (boxes[i] == '1') {
                count_ones_right -= 1;
            }

            ans[i] = ans[i - 1] - count_ones_right + count_ones_left;

            if (boxes[i] == '1') {
                ans[i] -= 1;
            }
        }

        return ans;
    }
};

Сложность алгоритма по времени - O(N)

В данном случае нам потребовалось всего два прохода по массиву boxes, значит сложность - O(N + N), то есть O(N).

Сложность алгоритма по памяти - O(1)

Дополнительные переменные - два счетчика, количество выделяемой под них памяти не зависит от размера входных параметров, поэтому сложность тут O(1).