88. Объединить отсортированный массив [leetcode] [c++]

Задача: здесь

Я успешно получил рабочий код на локальном компьютере, а также на платформе в первых трех случаях попытки моего решения. Но если я запущу Submit, я получу ошибку времени выполнения неопределенного поведения в этом случае: nums1 = [2,0], m = 1, nums2 = [1], n = 1. Насколько я понимаю, результат должен быть nums1 = [1,2], и я получил его на локальной машине без каких-либо ошибок. почему это происходит?

class Solution {
public:
    void merge(std::vector<int>& nums1, int m, std::vector<int>& nums2, int n);
};

void Solution::merge(std::vector<int>& nums1, int m, std::vector<int>& nums2, int n)
{
    int iNums1 = m - 1;
    int iNums2 = n - 1;
    int iResult = m + n - 1;

    for (; iResult >= 0 && iNums2 >= 0; iResult--)
    {
        if (iNums2 < 0)
        {
            break;;
        }
        else if (iNums1 < 0 && iNums2 >= 0)
        {
            std::swap(nums2, nums1);
            break;
        }       
        else if (nums2[iNums2] > nums1[iNums1])
        {
            nums1[iResult] = nums2[iNums2--];
        }
        else if (iResult <= m && nums1[iResult - 1] <= nums2[iNums2] && nums1[iResult] > nums2[iNums2])
        {
            nums1[iResult + 1] = nums1[iResult];
            nums1[iResult] = nums2[iNums2--];
        }
    }
}

использовал тестовый пример на локальном компьютере, ошибок не возникло:

Solution s;
std::vector<int> nums1 = { 2, 0 };
std::vector<int> nums2 = { 1 };
std::vector<int> result = { 1, 2 };
s.merge(nums1, 1, nums2, 1);


переполнение буфера кучи включено nums1[iResult - 1]
Ahmed AEK 06.09.2024 09:19

Замените все ваши [i] на .at(i), а затем отладьте свой код. Вы должны знать, что C++ не помешает вам получить доступ к памяти, но это будет неопределенное поведение.

Pepijn Kramer 06.09.2024 09:22

Да, изменил доступ к элементу с оператора [] на метод «at» и получил этот UB в порядке else if (iResult <= m && nums1.at(iResult - 1) <= nums2.at(iNums2) && nums1.at(iResult) > nums2.at(iNums2)) Спасибо, надо об этом подумать.

Александр Максаков 06.09.2024 09:36

Вы не можете наблюдать отсутствие неопределенного поведения. Самое неприятное его проявление — когда программа вроде бы работает.

molbdnilo 06.09.2024 09:46

Вам необходимо добавить параметры компилятора, чтобы дезинфицирующие средства на вашем локальном компьютере могли воспроизводить отчеты UB из LeetCode.

Weijun Zhou 06.09.2024 12:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
5
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Менять местами nums1 и nums2 не имеет смысла, поскольку результат должен полностью храниться внутри nums1.

Кроме того, последнее условие не требуется; если следующий элемент, добавляемый к выводу, не из первого вектора, то он обязательно должен быть из второго. Осталось проверить границы: если в первом векторе не осталось элементов, мы можем брать элементы только из второго и наоборот.

for (; iResult >= 0 && iNums2 >= 0; iResult--) {      
    if (iNums1 < 0 || iNums2 >= 0 && nums2[iNums2] > nums1[iNums1]){
        nums1[iResult] = nums2[iNums2--];
    } else {
        nums1[iResult] = nums1[iNums1--];
    }
}

Это решение вызывает ошибку segfault в тестовом примере: ``` std::vector<int> nums1; std::vector<int> nums2 = { 1 }; std::vector<int> result = { 1 }; s.merge(nums1, 0, nums2, 1); ```

Александр Максаков 06.09.2024 12:17

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

Александр Максаков 06.09.2024 12:22

@АлександрМаксаков Это даже не действительный тестовый пример. nums1 должен иметь размер, равный n + m. Просто попробуйте отправить его на LeetCode.

Unmitigated 06.09.2024 12:28

странно но факт - на литкоде работает О.о и отправил

Александр Максаков 06.09.2024 12:32

да, nums1 в случае, если это должно быть {0}; например, nums1.size() = 1;

Александр Максаков 06.09.2024 12:36

Аннотированное решение

Исходный код ниже взят из ОП. Я добавил множество комментариев, объясняющих, что пошло не так.

Я использую операторы препроцессора #if-else-endif, чтобы изолировать плохой код и вставить свои исправления. Это означает, что вы можете скопировать и вставить это обратно в исходную программу, и все должно работать. Конечно, после того, как вы его очистите, он станет намного красивее.

void Solution::merge(std::vector<int>& nums1, int m, std::vector<int>& nums2, int n)
{
    int iNums1 = m - 1;
    int iNums2 = n - 1;
    int iResult = m + n - 1;

#if 0
    // This loop will stop early when `iNums1 >= 0 && iNums2 < 0`.
    for (; iResult >= 0 && iNums2 >= 0; iResult--)
    {
#else
    // If `iResult` is not negative, then you KNOW that either 
    // `iNums1` or `iNums2` (or both) are also non-negative.
    // So, keep looping!
    for (; iResult >= 0; iResult--)
    {
#endif
        if (iNums2 < 0)
        {
            // This is perfect. Note, however, when you had the wrong 
            // termination condition on the for-statement, this was, 
            // effectively, a "no-op." That's because `iNums2` could 
            // never be negative.
            break;;
        }
#if 0
        // There is nothing wrong with this test: the program will work 
        // fine if you leave it as it is. The second test, however, is 
        // redundant. We already know that `iNums2` in not negative.
        // We just tested it!
        else if (iNums1 < 0 && iNums2 >= 0)
#else
        else if (iNums1 < 0)
#endif
        {
#if 0
            // Swapping vectors is the wrong idea.
            // You will lose the data already present in `nums1`.
            std::swap(nums2, nums1);
            break;
#else
            // When `iNums1` goes negative, you can simply copy 
            // whatever remains in `nums2`.
            while (iResult >= 0) {
                nums1[iResult--] = nums2[iNums2--];
            }
            break;
#endif
        }
        else if (nums2[iNums2] > nums1[iNums1])
        {
            nums1[iResult] = nums2[iNums2--];
        }
#if 0
        // You are working too hard here. 
        else if (iResult <= m 
            && nums1[iResult - 1] <= nums2[iNums2] 
            && nums1[iResult] > nums2[iNums2])
        {
            nums1[iResult + 1] = nums1[iResult];
            nums1[iResult] = nums2[iNums2--];
        }
#else
        else
        {
            // Since we know that `nums2[iNums2] > nums1[iNums1]` 
            // is false (because we just tested it), then 
            // `nums1[iNums1]` must be the one that goes in the 
            // result.
            nums1[iResult] = nums1[iNums1--];
        }
#endif
    }
}

Фреймворк для решения проблем LeetCode

Я использую одну и ту же «инфраструктуру» для всех задач LeetCode.

Все начинается с функции main, простого драйвера, который позволяет мне добавлять столько примеров, сколько я захочу. Я начал с трех примеров с сайта LeetCode. Затем я добавил неудавшийся тест из ОП и неизбежный (и, смею сказать, патологический) граничный тест, где оба вектора пусты.

int main()
{
    std::cout << "LeetCode 88. Merge Sorted Array"
        "\nhttps://leetcode.com/problems/permutations-ii/"
        "\n\n";
    solve("Example 1", { 1,2,3,0,0,0 }, 3, { 2,5,6 }, 3);
    solve("Example 2", { 1 }, 1, {}, 0);
    solve("Example 3", { 0 }, 0, { 1 }, 1);
    solve("Example 4", { 2,0 }, 1, { 1 }, 1);
    solve("Example 5", {}, 0, {}, 0);
}

Функция solve организует решение и отображает результаты так же, как примеры показаны на веб-сайте LeetCode.

Я использую параметры по значению для передаваемых векторов. Таким образом, аргументы, используемые в функции main, могут быть заключены в списки инициализаторов.

void solve(
    std::string_view heading,
    std::vector<int> nums1,
    int const m,
    std::vector<int> nums2,
    int const n)
{
    std::cout
        << heading
        << "\n"
        << "\n  Input: nums1 = " << put_vector(nums1)
        << ", m = " << m
        << ", nums2 = " << put_vector(nums2)
        << ", n = " << n;
    Solution{}.merge(nums1, m, nums2, n);
    std::cout
        << "\n  Output: " << put_vector(nums1)
        << "\n\n";
}

Небольшой набор инструментов используется снова и снова. На этот раз это был шаблон класса put_vector, реализованный в стиле манипулятора ввода-вывода.

Другие инструменты, которые у меня есть, включают помощники для ужасных связанных списков и двоичных деревьев, предпочитаемых LeetCode.

template< typename T >
class put_vector
{
    std::vector<T> const& v;
public:
    put_vector(std::vector<T> const& v) : v{ v } {}
private:
    void put(std::ostream& ost) const {
        ost.put('[');
        if (!v.empty()) {
            ost << v.front();
            for (std::size_t i{ 1u }; i < v.size(); ++i) {
                ost << ',' << v[i];
            }
        }
        ost.put(']');
    }
    friend std::ostream& operator<< (std::ostream& ost, put_vector const& pv) {
        pv.put(ost);
        return ost;
    }
};

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

// 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 merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    }
};

Поскольку я могу скопировать все шаблоны из предыдущих решений LeetCode, эти предварительные действия занимают всего около 5-10 минут. Тогда я смогу запустить программу.

LeetCode 88. Merge Sorted Array
https://leetcode.com/problems/permutations-ii/

Example 1

  Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
  Output: [1,2,3,0,0,0]

Example 2

  Input: nums1 = [1], m = 1, nums2 = [], n = 0
  Output: [1]

Example 3

  Input: nums1 = [0], m = 0, nums2 = [1], n = 1
  Output: [0]

Example 4

  Input: nums1 = [2,0], m = 1, nums2 = [1], n = 1
  Output: [2,0]

Example 5

  Input: nums1 = [], m = 0, nums2 = [], n = 0
  Output: []

Теперь начинается самое интересное...

спасибо, это может быть полезнее, чем мои функции testCase())

Александр Максаков 06.09.2024 15:17

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