Проблема с обрезкой стержня: альтернативное повторение в интервью

Задача разрезания стержня описана в разделе 15.1 книги Кармен и др. «Введение в алгоритмы». Нам дан массив p, где p[j] представляет собой деньги, которые мы получим за стержень длины j. Учитывая стержень длиной n, какую наибольшую сумму денег можно заработать, оптимально разрезав его и продав куски?

Рекурсия, описанная в книге, на которой основано решение, описывает массив динамического программирования x, где x[j] представляет собой наибольшую сумму денег, которую можно заработать, разрезав и продав стержень длины j. Взяв максимум для различных вариантов первого разреза, мы получаем повторение:

x[n] = max(p[i] +x[n-i]); 0 <= я <= п

И тогда решение основано на этом.

Я спросил об этом в интервью, и человек рассказал следующее:

х[n] = max(x[i] +x[n-i]); 1 <= я <= n-1

Я не вижу веской причины, по которой второй повтор не сработает. Мы можем думать об этом как об условии наличия разреза в позиции i. Кажется, что программа, которую мы создадим, будет менее эффективной, чем программа, основанная на исходном повторении.

Есть ли способ количественно оценить, насколько хуже будет программа, основанная на втором повторении, и почему ее использование может быть плохой идеей?

Первое повторение неверно, потому что оно циклическое (установите i=0 и посмотрите). Предложенная рекурсия неверна, поскольку не учитывает возможность вообще не перерезать стержень, но идея поправима, если взять x[n] = max(p[n],max(x[i] +x[n-i])). Кроме того, по симметрии вам нужно запустить i только до n/2.

n. m. could be an AI 25.05.2024 08:19

p[0] определен? Я надеюсь, что оно не ненулевое, если оно...

trincot 25.05.2024 09:12
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Как уже отмечалось, оба рекуррентных отношения имеют проблемы:

  1. x[n] = max(p[i] + x[n-i]); 0 <= i <= n

    Здесь i может быть 0, что будет определять x[n] в терминах самого себя (бесконечная рекурсия). Не уточняется, предусмотрен ли p[0]. Если он предусмотрен, он обязательно должен быть равен 0, чтобы иметь смысл.

    Кроме того, i может быть n, что приведет к запросу x[0], но тогда нам нужно определить x[0] как отдельный базовый случай, поскольку мы уже исключили, что i может быть 0 в предыдущем пункте. Конечно, тогда x[0] следует определить как 0.

    x[0] = 0
    x[n] = max(p[i] + x[n-i]); n > 0, 0 < i <= n

    Поскольку x[n] = p[n] сейчас рассматривается как кандидат, и это также базовый случай (n == 0), мы могли бы встроить этот параметр в операцию max и рассматривать i < n только для других кандидатов:

    x[n] = max(p[n], p[i] + x[n-i] for 0 < i < n)


  2. x[n] = max(x[i] + x[n-i]); 1 <= i <= n-1

    Этот предотвращает граничные проблемы для i, но не рассматривает случай, когда стержень размера n не следует обрезать, и в этом случае x[n] = p[n].

    Поскольку условия суммы взаимозаменяемы, мы можем ограничить i до n/2, но тогда все равно придется учитывать x[n] = p[n].

    x[n] = max(p[n], x[i] + x[n-i] for 0 < i <= n / 2)

В итоге у нас есть две альтернативы:

  1. x[n] = max(p[n], p[i] + x[n-i] for 0 < i < n)
  2. x[n] = max(p[n], x[i] + x[n-i] for 0 < i <= n / 2)

Второе повторение должно проверять меньше кандидатов, чем первое, хотя эта разница не существенна с точки зрения временной сложности. Оба имеют временную сложность O(𝑛²) (с табулированием динамического программирования).

Вот стресс-тест реализаций обоих в JavaScript:

function solve1(p, size) {
    const x = Array(size + 1);
    for (let n = 0; n <= size; n++) {
        x[n] = p[n];
        for (let i = n - 1; i > 0; i--) {
            x[n] = Math.max(x[n], p[i] + x[n - i]); 
        }
    }
    return x[size];
}

function solve2(p, size) {
    const x = Array(size + 1);
    for (let n = 0; n <= size; n++) {
        x[n] = p[n];
        for (let i = n >> 1; i > 0; i--) {
            x[n] = Math.max(x[n], x[i] + x[n - i]); 
        }
    }
    return x[size];
}

// Bulk test with random sampling of p for a rod of size 100.
let size = 100;
let start;
let time1 = 0, time2 = 0; // Accumulated time spent on execution
for (let test = 1; test <= 10000; test++) {
    // Define p with random values, sorted
    let p = Array.from({length: size + 1}, () => Math.floor(Math.random() * 1000))
                 .sort((a,b) => a-b);
    p[0] = 0; // This is required (in case an algorithm would read it)

    // Execute and measure the first solution:
    start = performance.now();
    let a = solve1(p, size);
    time1 += performance.now() - start;

    // Execute and measure the second solution:    
    start = performance.now();
    let b = solve2(p, size);
    time2 += performance.now() - start;
    
    // Assert that both return the same result
    console.assert(a === b, "solve1 and solve2 output different results!");
}
console.info("Milliseconds on solve1:", Math.ceil(time1));
console.info("Milliseconds on solve2:", Math.ceil(time2));

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