Поворот массива вправо на K мест (грубая сила)

Я нашел этот вопрос в leetcode, так как сейчас изучаю массивы. Похоже, проблема в блоке shifting данного кода.

Example 1:

Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2:

Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation: 
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = sizeof(nums)/sizeof(nums[0]);
        k = k%n;
        vector<int> temp;
        //storing the elements of the array till k places
        for(int i = 0; i < k; i++) {
            temp.push_back(nums[i]);
        }

        //shifting by k places
        for(int i = k; i<n;i++) {
            nums[i+k] = nums[i];
        }

        //putting the temp back to the place
        int j = 0;
        for(int i = n-k; i < n; i++) {
            nums[i] = nums[j];
            j++;
        }
    }
};

Я решил повернуть массив, оставленный на k мест, в моем локальном редакторе. Я прекрасно понимаю решение.

Здесь, насколько я знаю, происходит переполнение памяти.

Я уверен, что остальная часть кода работает нормально, за исключением блока shifting.

int n = sizeof(nums)/sizeof(nums[0]); максимально неправильно.
463035818_is_not_an_ai 21.06.2024 12:47

Вы должны определить int n = nums.size(); + что, по вашему мнению, происходит, например. вектор размера 10 с k = 5 в цикле for(int i = k; i<n;i++)? Представьте себе, что когда i равно 9, то nums[i+k] все еще находится внутри вектора?

Atmo 21.06.2024 12:47
sizeof(nums)/sizeof(nums[0]) предназначен для массивов C, а не std::vector. Для std::vector используйте для этого метод size().
wohlstad 21.06.2024 12:47

См. std::rotate.

Jarod42 21.06.2024 14:06
If (k < size(vec)) std::rotate(begin(vec), begin(vec)+k, end(vec)); и получайте удовольствие. Не забудьте #include <algorithm>.
Red.Wave 21.06.2024 14:12

@Red.Wave Я думаю, вы пропустили, что ОП призывает к повороту вправо. std::rotate выполняет операцию поворота влево. Поскольку ОП требует поворота вправо, если вы собираетесь использовать std::rotate, вам придется использовать begin(vec) + (vec.size() - k) в качестве среднего аргумента.

tbxfreeware 21.06.2024 15:33

@tbxfreeware Для поворота влево и вправо используйте обратные итераторы: std::rotate(rbegin(vec), rbegin(vec)+k, rend(vec));.

Red.Wave 21.06.2024 15:37

Похоже, проблема заключается в блоке сдвига данного кода. Это однострочное решение, уже решенное с помощью std::rotate. Посмотрите это

PaulMcKenzie 21.06.2024 16:38

Я нашел этот вопрос в leetcode, поскольку сейчас изучаю массивы. Leetcode не учит вас C++. По сути, это набор случайных вопросов-головоломок для людей, которые уже имеют опыт работы с языком, который они будут использовать для решения головоломки. Он не предназначен для начинающих программистов — если вы только начинаете изучать массивы, тратить время на решение головоломок, ориентированных на опытных программистов, не поможет. Кроме того, вопросы Leetcode разработаны таким образом, что «грубая сила» почти никогда не сработает при заданных ограничениях, и необходимо использовать более сложные подходы.

PaulMcKenzie 21.06.2024 16:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
104
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Предположим, ваш вектор имеет длину 5 элементов, вы делаете правильный int n = nums.size();, а затем хотите повернуть на k=3. В вашем цикле for, поскольку i == k и k == 3, ваша первая итерация затем обращается к num[3+3], который выходит за пределы и, следовательно, выходит из строя.

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

Ваш подход верен, но вы поменялись ролями k и n - k.

Как было объяснено в комментариях, вы также неправильно применили идиому языка C для определения размера массива — std::vector. std::vector несет в себе свой размер. Вы можете получить его, вызвав функцию-член size().

  • На первом этапе поместите первые элементы n - k в вектор temp.

  • На втором этапе скопируйте последние элементы k в начало вектора.

  • На последнем этапе скопируйте элементы n - k из временного вектора в конец вектора nums (начиная с нижнего индекса k).

При вашем подходе нет необходимости использовать оператор %.

Подстрочные индексы должны использовать тип std::size_t, который находится в заголовке <cstddef>. Следующая программа аккуратно приводит k к типу std::size_t и использует этот тип для всех переменных управления циклом.

// main.cpp
#include <cassert>
#include <cstddef>
#include <iostream>
#include <string_view>
#include <utility>
#include <vector>

// This using declaration allows the LeetCode Solution 
// to be used without modifying its function signature.
// 
// (Shame on you, LeetCode, for teaching programmers to 
// rely on `using namespace std;`.)
using std::vector;

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        // Preconditions:
        // `nums.size()` lies on the interval [0, 10^5]
        // `k` lies on the interval [0, 10^5]
        assert(nums.size() <= 100'000u);
        assert(k >= 0 && k <= 100'000);

        // Vector size (and subscripts) use type `std::size_t`, from 
        // header <cstddef>. Force `kk` to the half-open interval [0, n).
        std::size_t const n{ nums.size() };
        std::size_t const kk{ static_cast<std::size_t>(k) % n };

        // Treat the "no-rotatation" circumstance as a special case.
        if (kk == 0u)
            return;

        // Copy the first (n - kk) elements into a temporary vector.
        vector<int> temp;
        for (std::size_t i{}; i < (n - kk); ++i) {
            temp.push_back(nums[i]);
        }

        // Copy the last kk elements to the front of the vector.
        std::size_t from{ n - kk };
        for (std::size_t i{}; i < kk; ++i)
            nums[i] = nums[from++];

        // Copy (n - kk) elements from the temporary vector to the 
        // end of vector nums (beginning with subscript `kk`).
        from = 0u;
        for (std::size_t i{ kk }; i < n; ++i)
            nums[i] = temp[from++];
    }
};
class put_vector
{
    std::vector<int> const& v;
public:
    put_vector(std::vector<int> const& v) : v(v) {}
    friend std::ostream& operator<< (std::ostream& ost, put_vector const& pv) {
        ost.put('[');
        if (!pv.v.empty()) {
            ost << pv.v.front();
            for (std::size_t i{ 1u }; i < pv.v.size(); ++i)
                ost << ',' << pv.v[i];
        }
        ost.put(']');
        return ost;
    }
};
void solve(
    std::string_view heading,
    std::vector<int> nums,
    int k)
{
    std::cout
        << heading
        << '\n'
        << "\n  Input: nums = " << put_vector(nums) << ", k = " << k;
    Solution{}.rotate(nums, k);
    std::cout
        << "\n  Output: " << put_vector(nums)
        << "\n\n";
}
int main() {
    std::cout << "LeetCode 189. Rotate Array"
        "\nhttps://leetcode.com/problems/rotate-array/"
        "\n\n";
    solve("Example 1", { 1,2,3,4,5,6,7 }, 3);
    solve("Example 2", { -1,-100,3,99 }, 2);
}
// end file: main.cpp

Выход

Вывод соответствует примерам с сайта LeetCode.

LeetCode 189. Rotate Array
https://leetcode.com/problems/rotate-array/

Example 1

  Input: nums = [1,2,3,4,5,6,7], k = 3
  Output: [5,6,7,1,2,3,4]

Example 2

  Input: nums = [-1,-100,3,99], k = 2
  Output: [3,99,-1,-100]

std::copy

В комментариях ниже @Evg указывает, что векторные элементы можно копировать с помощью std::copy (и итераторов). Я избегал использования вышеперечисленных, потому что хотел остаться в стиле ФП. Однако если они вас устраивают, программу можно значительно упростить.

std::copy позволяет удалить циклы, и вместе с ними полученный код легче читать и легче поддерживать.

Как показано ниже, std::copy принимает три аргумента, каждый из которых является итератором. Первые два описывают диапазон копирования: [первый, последний). Третий определяет пункт назначения.

// Copy the first (n - k) elements into a temporary vector.
auto const nk{ n - k };
vector<int> temp(nk);
std::copy(nums.cbegin(), nums.cbegin() + nk, temp.begin());

// Copy the last k elements to the front of the vector.
std::copy(nums.cbegin() + nk, nums.cend(), nums.begin());

// Copy (n - k) elements from the temporary vector to the 
// end of vector `nums` (beginning with subscript `k`).
std::copy(temp.cbegin(), temp.cend(), nums.begin() + k);

Примечание

Как предложил @Jarod42, использование std::rotate сводит это к одной строке.

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

Evg 21.06.2024 15:00

Согласованный. Я пытался оставаться в рамках стиля, используемого ОП. ;)

tbxfreeware 21.06.2024 15:09

Чтобы решить эту проблему более эффективно, используйте следующий трюк:

  • Создайте функцию void reverse(std::vector<int>& nums, int start, int end), которая работает за временную сложность O(n/2).
  • Обратный массив от индекса 0 до n
  • Переверните первое разбиение из [0...k), а второе — из [k, n)

Готово, вектор сдвигается на k элементов.

Другие вопросы по теме