#algorithm #optimization #dynamic-programming #code-analysis
#алгоритм #оптимизация #динамическое программирование #анализ кода
Вопрос:
http://datagenetics.com/blog/july22012/index.html
Я использую указанную ссылку, чтобы понять проблему удаления яиц. Я также просмотрел онлайн-код, который я понимаю в достаточной степени (рекурсия немного сбивает с толку), но, похоже, я не могу понять несколько основных моментов в этой проблеме с удалением яиц.
Предположим, у нас есть 100 этажей
-
Для 2 яиц мы говорим, что начинаем с 14, а затем переходим к 14 (14-1). Я понимаю, почему мы делаем это, чтобы сохранить равномерное время в худшем случае и все такое. Однако, с чего мы начнем для трех яиц? Формула показывает, что у 3 яиц будет максимум 9 попыток в худшем случае. Очевидно, что мы не начинаем с 9, потому что переход на 9 (9 — 1 ) не дает нам последовательных 9 испытаний из 100, так с чего же нам начать для 3? Не только это, но и как мы это выясняем?
-
Похоже, что для 3 яиц мы проводим несколько испытаний, пока проблема не вырождается в 2 яйца и x количество этажей. Концептуально это имеет смысл, но я не понимаю, как визуализировать или реализовать это
-
Я должен найти последовательность попыток в наихудшем сценарии и реализовать ее, поэтому я хочу визуализировать попытки.
Я надеюсь, что это понятно. Это моя первая пуля, которая является моей главной проблемой. Пожалуйста, дайте мне знать, если я упущу какую-либо информацию, и я отредактирую ее.
Комментарии:
1. Вы не удовлетворены полученными ответами?
2. Я смущен несколькими моментами, если к концу пятницы я не получу никаких других ответов, я проголосую за лучший ответ (который имеет наибольший смысл)
3. Если вы в замешательстве, оставьте комментарий к запутанному ответу и задайте вопрос, не игнорируйте его. Кроме того, вы можете проголосовать (за и против) за несколько ответов, как считаете нужным — если ответ полезен, поддержите его, даже если вам нужна дополнительная информация. Если вы ищете больше ответов, вам нужно вызвать больший интерес к вашему вопросу. У меня есть простой способ сделать это с помощью награды. Удачи!
4. Я согласен с @Amit, на какой концепции вы зацикливаетесь? возможно, мы могли бы помочь.
5. Было бы лучше задать вопрос, если бы он суммировал проблему с удалением яиц на случай, если ссылка когда-либо устареет.
Ответ №1:
Уравнение n(n 1)/2 = F может использоваться только для решения минимального наихудшего числа выпадений в случае 2 яиц. Здесь также находится один из этажей, с которого вы можете сбросить 1-е яйцо из-за упомянутой вами однородности. Вы знаете, что можете упасть с 14-го этажа, потому что, если он треснет, у вас будет, в худшем случае, еще 13 падений. Но если он не треснет, и вы подниметесь на 13 этажей, и он треснет, у вас будет в худшем случае еще 12 выпадений, а за плечами уже 2. С помощью этого шаблона вы можете видеть, что, сбрасывая яйцо с n-го этажа, затем с n (n-1) этажа, затем с n (n-1) (n-2) этажа, ваш худший случай остается неизменным при каждом пороге.
Это то, чего вы хотите достичь, независимо от того, со сколькими яйцами вы начинаете, но нахождение оптимального этажа (n), который делает это условие истинным (которое на самом деле может быть выражено в виде диапазона, как указал @Amit), не может быть рассчитано с замкнутым рядом, как это может быть для 2 яиц. Важно отметить, что даже в случае (n 1) n = F, n является лишь одним из многих ответов для возможного значения первого этажа. Мы удобно говорим, что это ответ, возможно, небрежно, потому что мы можем доказать, что это правда, используя относительно простую серию.
Итак, давайте воспользуемся более общим подходом для оценки минимального наихудшего числа выпадений, а затем посмотрим, на каких этажах, как мы знаем, этого можно достичь. Допустим, у нас есть функция g(floors, eggs)
, которая возвращает минимальное количество выпадений яиц в наихудшем случае, необходимое для определенного количества этажей. Мы можем с уверенностью сказать, что если eggs = 1
, то в худшем случае нам придется сбрасывать яйца с каждого этажа, чтобы найти пороговое значение, поэтому g(floors, 1) = floors
это верно для любого значения floors
. Мы также знаем, что если у нас есть 1 этаж для тестирования, всегда потребуется только одно падение, поэтому g(1, eggs) = 1
. Помимо этих двух случаев мы знаем следующее:
g(floors, eggs) = Min(n = 1->floors : 1 max(g(n-1,e-1),g(floors-n, e)))
Почему это работает? Учитывая общее количество этажей, нужно просмотреть каждый из них и посмотреть, какой наихудший вариант для разбивания яйца на каждом. Это делается путем сравнения наихудшего случая, если он трескается, или g([current floor]-1, eggs-1)
, с наихудшим случаем, если он не трескается, или g(floors-[current floor], eggs)
.
Максимальное из этих двух значений будет наихудшим сценарием для этого конкретного этажа. Если мы отследим глобальный минимум каждого из этих максимумов, мы найдем наименьшие потери, необходимые для наихудшего случая. Этаж (ы), на котором это происходит, является оптимальным этажом для сброса яйца. Теперь давайте подключим eggs = 2
эту функцию, чтобы лучше понять, почему это работает, и почему мы также можем представлять минимальное количество выпадений в худшем случае при запуске с 2 яйцами в виде серии.
Когда у нас будет ровно 2 яйца, мы всегда будем сравнивать g([current floor]-1, 1)
с g(floors-[current floor], 2)
. Это немного упрощает задачу, потому что мы точно знаем, что будет в худшем случае, если мы разобьем яйцо на текущем этаже:
*worst case drops required* = g(cf-1, 1) 1 = 1 (cf-1)
примечание -здесь мы добавляем 1, потому что мы уже выполнили 1 удаление к тому времени, когда сможем протестировать оставшиеся этажи ниже.
Мы также знаем, что две функции, которые мы сравниваем на каждом этаже (cf), монотонно движутся в двух разных направлениях для любого фиксированного количества общих этажей F. Это должно быть правдой, потому что:
g(cf d, 1) > g(cf, 1)
для любых положительных значений cf и d эта функция увеличивается по мере увеличения cf.
g(F-(cf d),2) < g(F-cf,2)
, таким образом , эта функция всегда уменьшается по мере увеличения cf.
Вышесказанное важно, потому что это заставляет нас осознать, что минимально максимальная из этих двух функций будет выполняться на этаже (назовем это оптимальным этажом, of), где они возвращают значения, наиболее близкие друг к другу, можно даже сказать, равные друг другу! Используя это, мы можем приблизиться к тому, что минимум возникает, когда:
g (из1-1, 1) ~= из1-1 ~= g (F- из1, 2) ~= 1 (из2-1) ~= 1 g(F- из1 — из2, 2) ~= 2 (из3 -1) ~=…..~= из2 -> самое дальнее значение справа представляет наихудший случай, если яйцо не треснет ни при одном из оптимальных порогов, тогда мы израсходуем 2 числа выпадений, чтобы добраться до этой точки, не считая самого первого выпадения.
где 1 — теоретический уровень, на котором может произойти первое отбрасывание, чтобы минимизировать наихудший случай, а 1 из 2 — это место, где должно произойти второе отбрасывание (учитывая невозможность взлома при 1), вплоть до 1 из 2 … из n = F. Давайте теперь рассмотрим взаимосвязь между из1 и из 2:
(из1-1) = 1 (из2-1), поэтому из2 = из1-1
аналогично
из3 = из2 -1 = из1 — 2
наконец, мы можем сказать, что в целом
изn = из1-(n-1)
Мы знаем, что если мы прошли все пороговые уровни, кроме последнего, и ни одно яйцо не разбилось, то мы находимся на1-м отбрасывании.
из1 = из1-(из1-1) =1 => этот элемент является последним в нашей серии
Последовательность из1 из2 … изn может быть записана как из1 (из1-1) (из1 -2) …. 1 = F, которое, как мы знаем, может быть выражено как (из1) (из1 1) / 2 = F. Это позволяет найти как минимальное количество выпадений в наихудшем случае, так и оптимальный этаж для сброса первого яйца. простое упражнение по включению F в эту формулу.
Хорошо, теперь давайте используем ту же функцию, когда количество яиц равно трем! К сожалению, оказывается, что вы упираетесь в стену на самом первом шаге. Помните, когда мы все еще сравнивали g([current floor]-1, eggs-1)
с g(floors-[current floor], eggs)
? Ну, скажем eggs = 3
теперь, чтобы вы сравнивали g(floors-[current floor], 3)
и g([current floor]-1, 2)
.
Мы не можем свести ни одну из этих функций в ряд с решением замкнутой формы в качестве функции
g(F - cf, 3)
для решения требуется по крайней мере один уровень рекурсии, поэтому, к чему бы мы его ни сводили, в нем всегда будет термин с функцией g. С другой стороны, если мы попытаемся использовать тот факт, что для любого
g(f-1, 2)
существует n
где (n 1)n/2 = f-1
, где n — минимальное количество выпадений в наихудшем случае.
Если мы изменим (n 1)n/2 = f-1
на n = 1/2(sqrt(8f-7)-1) = g(f-1, 2)
потенциально мы могли бы попытаться установить g (из1-1, 2) равным 1 g (из2-1, 2), чтобы найти 2 как функцию от1, аналогично тому, как мы нашли 2, выраженные через 1, когда мы начинали с 2 яиц. Если вы помните, мы поместили все «оптимальные» этажи для отбрасывания, выраженные в терминах оптимального этажа для первого отбрасывания, в ряд, который, как оказалось, имел решение замкнутой формы. С 3 яйцами нам не повезло, так как это приводит к «уродливой» серии, которую невозможно решить без рекурсии. Это отличается от случая, когда мы начинали с 2 яиц, потому что мы смогли сократить g(cf-1, 1) 1
до всего 1 (cf-1)
. Это помогло нам создать серию, у которой было решение в закрытой форме.
Поэтому нет изящного вывода, который можно использовать для нахождения оптимального первого удаления, как в случае с 2 яйцами. Ниже я написал короткий алгоритм, который выводит как минимальное количество выпадений в наихудшем случае, так и оптимальное значение для первого выпадения (часто их может быть больше одного, но я всегда возвращаю последнее). Как только вы введете значение, отличное от 2 для e, вы можете заметить, что они не обязательно будут равны друг другу.
var optimumFloorForFirstDrop = 0;
var F = 24;
console.log("Minimum worst case number of drops: " findBestWorstCase(F,3) ", optimum floor for first drop: " optimumFloorForFirstDrop);
function findBestWorstCase(n, e){
//if we have 0 or 1 floors or one egg then return the number of floor...this is pretty intuitive
if(n < 2 || e == 1) return n;
//we want to go through every floor and keep track of the minimum for a particular n of the max function below
var min = n;
for(var i = 1; i <= n; i ){
var tmpMax = 1 Math.max(findBestWorstCase(i-1, e-1),findBestWorstCase(n-i, e));
if(tmpMax <= min){
min = tmpMax;
if(n==F) optimumFloorForFirstDrop = i;
}
}
return min;
}
Комментарии:
1. Ваше первое утверждение неверно. Вам не обязательно начинать с 14-го этажа (см. Мой ответ для доказательства и деталей).
2. Я понимаю, что существует диапазон этажей, с которых вы можете выполнить удаление, потому что фактическое n как функция f на самом деле является ступенчатой функцией, но я пытаюсь объяснить интуицию, лежащую в основе того, почему в ванильном ответе на эту проблему ответ равен всего 14, я уточню, что вам не нужно сбрасывать с этого этажа, но вы должны уметь и все еще быть в состоянии достичь минимума.
3. @Amit это лучше?
4. Я думаю… Я только начал читать этот длинный ответ (мой тоже длинный, я знаю), потому что я пытался понять комментарий OP. Извините, но это слишком долго для меня, чтобы «просмотреть» 😉
Ответ №2:
Давайте сначала рассмотрим несколько примеров:
Если у вас 1 яйцо, сколько бросков вам нужно для здания в 2 этажа? 3 этажа? 4?
Очевидно, что это просто. Вам нужно 2, 3 и 4 броска соответственно, и, как правило, n бросков.
Что, если у вас есть 2 яйца? Сколько бросков для 2 этажей? 3? 4?
Очевидно, что 2 броска на 2 этажа… 3 все же интересно. Если кинуть яйцо со 2— й этаж, и она не ломается, то бросьте ее с 3й этаж и вы знаете ответ (он либо сломается на 3 —й этаж, или не на всех). Если первая яйцеклетка разрывает на 2— й пол бросаю, ты бросаешь другое яйцо из первого этажа и снова, ты знаешь ответ (она или распадается на 1ст пол и это ответ или его нет, и ответ на 2— й этаж). Ха… так только 2 броска по 3 этажи. Но это не работает на 4-м этаже, и нам нужен еще один бросок. Этот дополнительный бросок может «купить нам» больше, чем просто один этаж. Это действительно приведет нас на 6-й этаж (скоро мы увидим, как это работает).
Также оказывается, что наличие большего количества яиц не будет иметь никакого значения для 6-этажного здания или меньше.
Предположим, мы знаем, сколько этажей мы можем покрыть m-1 яйцами и n-1 бросками, давайте назовем это h. Если у нас есть m яиц и n бросков, нашей оптимальной стратегией было бы пропустить первое яйцо с h 1-го этажа — это самый высокий этаж, на который мы можем пойти. Если яйцо разобьется, у нас будет достаточно яиц (m-1) и достаточное количество бросков (n-1), чтобы найти ответ на оставшихся h этажах. Наш следующий шаг, если яйцо не разобьется, — подняться на достаточное количество этажей, чтобы нас накрыло (m, n-1), и продолжать делать это, пока у нас не останется бросков. Таким образом, мы достигнем максимального покрытия при любой комбинации яиц и бросков.
Это объясняет нашу оптимальную стратегию, но мы не определили, сколько этажей будет (m, n) покрывать. Хотя это довольно просто: (m-1, n-1) покроет несколько h этажей, сам бросок (m, n) будет составлять h 1 этаж, а остальные броски позволят использовать дополнительные (m, n-1) этажи. К настоящему времени моделирование проблемы должно быть достаточно ясным, и мы можем определить простую рекурсивную функцию для вычисления максимальной высоты покрытия:
function maxHeightByEggThrows(eggs, throws) {
if(eggs === 0 || throws === 0)
return 0;
return maxHeightByEggThrows(eggs - 1, throws - 1) 1
maxHeightByEggThrows(eggs, throws - 1);
}
И это работает безупречно, но это плохая, неэффективная реализация. Давайте попробуем DP:
function maxHeightByEggThrowsDP(eggs, throws) {
let eggThrows = [[]];
for(let i = 0; i < throws; i ) {
// A single egg can cover has many floors has throws are allowed
eggThrows[0].push(i 1);
}
for(let i = 1; i < eggs; i ) {
// Any number of eggs can only cover 1 floor with a single throw
eggThrows.push([1]);
}
for(let i = 1; i < throws; i ) {
for(let j = 1; j < eggs; j ) {
eggThrows[j][i] = eggThrows[j - 1][i - 1] eggThrows[j][i - 1] 1;
}
}
return eggThrows[eggs - 1][throws - 1];
}
Это выглядит не так красиво, но это намного лучше по соображениям производительности, и мы можем использовать такую реализацию для возврата всей таблицы и ее отображения:
function maxHeightByEggThrowsDP(eggs, throws) {
let eggThrows = [[]];
for (let i = 0; i < throws; i ) {
// A single egg can cover has many floors has throws are allowed
eggThrows[0].push(i 1);
}
for (let i = 1; i < eggs; i ) {
// Any number of eggs can only cover 1 floor with a single throw
eggThrows.push([1]);
}
for (let i = 1; i < throws; i ) {
for (let j = 1; j < eggs; j ) {
eggThrows[j][i] = eggThrows[j - 1][i - 1] eggThrows[j][i - 1] 1;
}
}
return eggThrows;
}
const eggs = 10;
const throws = 15;
let eggThrows = maxHeightByEggThrowsDP(eggs, throws);
// display our data (boilerplate code)
// add a "row header" so we can read the egg count
eggThrows.forEach((row, i) => row.unshift(i 1));
d3.select('#eggThrows>tbody').selectAll('tr').data(eggThrows).enter().append('tr').selectAll('td').data(d => d).enter().append('td').text(t => t);
d3.select('#throwsHeader').attr('colSpan', throws);
#eggThrows > thead td {
padding: 5px;
background-color: #404040;
color: white;
}
#eggThrows > tbody td {
padding: 5px;
background-color: #C0C0C0;
}
#eggThrows > tbody td:first-child {
background-color: #C0FF30;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<table id="eggThrows"><thead><tr><td>Eggs</td><td id="throwsHeader">Throws</td></tr></thead><tbody></tbody></table>
Итак, мы знаем максимальную высоту, но нам нужен первый этаж для броска… Оказывается, есть несколько вариантов. Например, теперь мы можем видеть, что в классическом случае «2 яйца 14 бросков» мы действительно можем получить ответ для 105 этажей, а не только для 100. Мы также знаем, что 2 яйца и 13 бросков покрывают 91 этаж, поэтому мы могли бы бросить первое яйцо с 9-го этажа и все равно найти решение за 14 бросков.
И теперь мы можем ответить на вопрос:
Первый этаж, с которого нужно бросать, не выше (maxHeightByEggThrows(m-1, n-1) 1) и не ниже ([высота здания] — maxHeightByEggThrows(m, n-1))
(для 3 яиц и 100 этажей это между 8-м и 37-м этажами)
Ответ №3:
Эта проблема может быть решена с помощью следующих 3 подходов (которые я знаю) :
- Динамическое программирование
- Решение с использованием двоичного дерева поиска
- Решение путем получения прямой математической формулы для максимального количества этажей, которые могут быть протестированы или покрыты заданным количеством яиц и заданным количеством капель
Позвольте мне сначала определить некоторые символы, которые используются в анализе, выполняемом впоследствии :
e = number of eggs
f = number of floors in building
n = number of egg drops
Fmax(e, n) = maximum number of floors that can be tested or covered with e eggs and n drops
Суть подхода динамического программирования заключается в следовании рекурсивной формуле для Fmax:
Fmax(e, n) = 1 Fmax(e-1, n-1) fmax(e, n-1)
И суть получения прямой математической формулы для Fmax заключается в следующей рекурсивной формуле для Fmax:
Fmax(e, n) = { ∑Fmax(e-1,i) for i = 1 to n } - Fmax(e-1, n) n
Альтернативное решение с использованием бинарного дерева поиска (BST) также возможно для этой проблемы. Чтобы облегчить наш анализ, давайте нарисуем BST с небольшими изменениями следующим образом:
1. If egg breaks then child node is drawn on left down side
2. If egg does not break then child node is drawn straight down side
Если мы рисуем BST с указанным выше видом представления, то ширина BST представляет количество яиц.
Любой BST с f числом узлов, нарисованный с использованием вышеуказанного представления и подверженный ширине ограничения BST <= e (количество яиц), является решением, но это может быть не оптимальное решение.
Следовательно, получение оптимального решения эквивалентно получению расположения узлов в BST с минимальной высотой, подверженной ограничению: ширина BST <= e
Более подробную информацию обо всех вышеупомянутых 3 подходах см. в моем блоге: 3 подхода к решению обобщенной проблемы удаления яиц
Ответ №4:
Вот алгоритм, который был реализован в swift (это включает в себя концепцию всех предыдущих алгоритмов):-
//Here n is number of eggs and k is number of floors
func eggDrop(_ n: Int, _ k: Int) -> Int {
var eggFloor: [[Int]] = Array(repeating:Array(repeating: 0, count: k 1),count: n 1)
if k == 1 {
return k
}
for i in 1...n {
eggFloor[i][1] = 1
eggFloor[i][0] = 0
}
for j in 1...k {
eggFloor[1][j] = j
}
for i in 2...n {
for j in 2...k {
eggFloor[i][j] = Int(INT_MAX)
for x in 1...j {
let attempts = 1 max(eggFloor[i-1][x-1], eggFloor[i][j-x])
if attempts < eggFloor[i][j] {
eggFloor[i][j] = attempts
}
}
}
}
return eggFloor[n][k]
}