← Назад

1470.Shuffle the Array

Ссылка на задачу: https://leetcode.com/problems/shuffle-the-array/description/


массивы

easy


Описание

Дан массив nums, состоящий из 2n элементов в формате [x1,x2,…,xn,y1,y2,…,yn].

Необходимо вернуть массив в формате [x1,y1,x2,y2,…,xn,yn].


Примеры

Пример 1

Input: n = 3

251347

Output:

235417

Объяснение: x1 = 2, x2 = 5, x3 = 1; y1 = 3, y2 = 4, y3 = 7, поэтому x1,y1,x2,y2,x3,y3 - это [2, 3, 5, 4, 1, 7]

Пример 2

Input: n = 4

12344321

Output:

14233241

Пример 3

Input: n = 2

1122

Output:

1212

Ограничения


Решение

Способ 1

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

Так, достаточно пробежаться по массиву двумя указателями, один взят для «иксов», второй - для «игреков». В результирующий мы будем подкладывать значения по очереди: сначала с помощью первого указателя («иксы»), затем второго («игреки»).

Алгоритм:

  1. Инициализировать дополнительный массив ans размером 2n и индекс k для присваивания значений ans

  2. Пробежаться по заданному массиву nums в диапазоне индексов от 0 до n (индекс i)

  3. k-тому элементу ans присвоить nums[i], инкрементировать k (это «иксы»)

  4. k-тому элементу ans присвоить уже nums[i + n] (это «игреки»)

Примечание: с точки зрения написания кода нам не нужно заводить две разных переменных под два разных указателя; первый указатель - это i, второй - i + n

Код:

python

class Solution:
    def shuffle(self, nums: List[int], n: int) -> List[int]:
        ans = [-1] * 2 * n
        k = 0

        for i in range(n):
            ans[k] = nums[i]
            k += 1

            ans[k] = nums[i + n]
            k += 1

        return ans

C++

class Solution {
public:
    vector<int> shuffle(vector<int>& nums, int n) {
        vector<int> ans(2 * n);
        int k = 0;
        for (int i = 0; i < n; ++i) {
            ans[k++] = nums[i];
            ans[k++] = nums[i + n];
        }
        return ans;
    }
};

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

Для реализации достаточно пробежаться вдоль исходного массива nums размером 2n всего один раз.

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

Почему O(N), ведь результат, который требуется вернуть в задаче, не должен вносить вклада в оценку сложности? Все дело в том, что в задаче требуется модифицировать исходный массив, а не составить новый на базе исходного. Так, массив ans здесь выступает в роли дополнительного объекта.


Способ 2

Можно ли нам каким-либо образом модифицировать исходный массив “на лету”?

У “иксов” индексы от 0 до n. После перестановки индекс каждого “икса” является четным, а “игрека” - нечетным.

Первые n элементов - “иксы”, остальные n - “игреки”. Так, мы снова можем заранее инициализировать указатель для “иксов” (i в диапазоне от 0 до n) и “игреков” (j в диапазоне от n до 2n), а в рамках одного перебора всех 2n элементов заданного массива присваивать либо по i (если текущий индекс перебора четный), либо по j (если текущий индекс перебора нечетный).

Проблема в том, что после присвоения элементу заданного массива уже с индексом 1 мы потеряем его старое значение.

Пример: [1,2,3,4,11,12,13,14], n = 4. Первый элемент имеет четный индекс 0, поэтому ему мы присваиваем значение по указателю i, то есть та же 1 (i теперь становится 1). Следующий элемент имеет уже нечетный индекс 1, поэтому ему нужно присвоить значение по j, то есть 11 (j теперь стал n + 1). После данных двух модификаций массив стал: [1,11,3,4,11,12,13,14].

Дальше у нас на рассмотрении элемент с индексом 2, снова четный, поэтому и присвоить нужно по i, который равен 1. Однако теперь, обратившись к массиву по индексу 1, мы увидим там 11, а не 2, как было раньше. Проблема кроется в том, что мы потеряли старое значение.

Задача сводится к вопросу: можно ли каким-то образом в одной ячейке массива хранить как старое значение, так и новое?

Да, можно. Это достигается путем использования “трюков"" с умножением и остатком от деления. На каждой итерации к элементу необходимо добавить слагаемое 1001 * (nums[index] % 1001). Почему именно такое слагаемое? Более подробно ответ на этот вопрос описан в задаче 1920 (https://leetcode.com/problems/build-array-from-permutation/description/). Приводить его здесь мы не будем, иначе способ 2 станет слишком огромен.

Алгоритм:

  1. Инициализировать два указателя: i с начальным значением 0 и j с начальным значением n

  2. Перебрать все элементы заданного массива индексом k

  3. На каждой итерации, где k кратен 2, прибавить к элементу исходного массива слагаемое 1001 * (nums[i] % 1001)

  4. На каждой итерации, где k не кратен 2, прибавить 1001 * (nums[j] % 1001)

  5. Перебрать все элементы исходного и уже модифицированного массива еще раз

  6. На каждой итерации разделить элемент нацело на 1001

Код:

python

class Solution:
    def shuffle(self, nums: List[int], n: int) -> List[int]:
        i = 0
        j = n

        for k in range(2 * n):
            if k % 2 == 0:
                nums[k] += 1001 * (nums[i] % 1001)
                i += 1
            else:
                nums[k] += 1001 * (nums[j] % 1001)
                j += 1
        
        for k in range(2 * n):
            nums[k] //= 1001
        
        return nums

C++

class Solution {
public:
    vector<int> shuffle(vector<int>& nums, int n) {
        int i = 0;
        int j = n;

        for (int k = 0; k < 2 * n; ++k) {
            if (k % 2 == 0) {
                nums[k] += 1001 * (nums[i++] % 1001);
            } else {
                nums[k] += 1001 * (nums[j++] % 1001);
            }
        }

        for (int k = 0; k < 2 * n; ++k) {
            nums[k] /= 1001;
        }
        return nums;
    }
};

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

Реализация сводится к двум полным переборам исходного массива размером 2n, значит сложность O(2N + 2N), константа выносится, результирующая сложность - O(N).

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

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