Люди публикуют свои решения, не сообщая никакой информации о том, как они были получены. Они не предоставляют гораздо больше объяснений.
К этой конкретной проблеме и большинству других можно подойти, используя следующую последовательность:
- Найти рекурсивное отношение
- Рекурсивный (сверху вниз)
- Рекурсивный + памятка (сверху вниз)
- Итеративный + памятка (снизу вверх)
- Итеративный + N переменных (снизу вверх)
Вопрос. Вы профессиональный грабитель, планирующий ограбить дома вдоль улицы. В каждом доме спрятана определенная сумма денег, единственное ограничение, которое мешает вам ограбить каждый из них, заключается в том, что в соседних домах подключены системы безопасности, и она автоматически свяжется с полицией, если два соседних дома будут взломаны в одну и ту же ночь.
Зная массив целых чисел nums
, представляющий сумму денег в каждом доме, верните максимальную сумму денег, которую вы можете ограбить сегодня вечером, не сообщая полиции.
Шаг 1. Выяснить рекурсивное отношение.
У грабителя есть 2 варианта: а) ограбить текущий дом i
; б) не грабить текущий дом.
Если выбран вариант «а», это означает, что она не может грабить предыдущий i-1
дом, но может безопасно перейти к дому перед предыдущим i-2
и получить всю совокупную добычу, которая последует.< br /> Если выбран вариант «b», грабитель получает всю возможную добычу от ограбления i-1
и всех следующих зданий.
Итак, все сводится к расчету того, что выгоднее:
- ограбление текущего дома + лут из домов предыдущего
- добыча от предыдущего ограбления дома и любая добыча, захваченная до этого
rob(i) = max( rob(i - 2) + nums[i], rob(i - 1) )
Шаг 2. Рекурсивное (сверху вниз)
Преобразование рекуррентного отношения из шага 1 не должно быть очень сложным.
def rob(nums):
return robdp(nums, nums.length - 1)
def robdp(nums, i):
if (i < 0):
return 0
return max(rob(nums, i - 2) + nums[i], rob(nums, i - 1))
Этот алгоритм будет обрабатывать одно и то же i
несколько раз, и его необходимо улучшить. Временная сложность: [заполнить]
Шаг 3. Рекурсивный + памятка (сверху вниз).
def rob(nums,n): memo = [-1]*(n+1) return robdp(nums, n- 1)
def robdp(int[] nums, int i): if (i < 0): return 0 if (memo[i] >= 0): return memo[i] result = max(rob(nums, i - 2) + nums[i], rob(nums, i - 1)) memo[i] = result return result
Гораздо лучше, это должно работать за O(n)
времени. Сложность пространства тоже O(n)
, из-за стека рекурсии попробуем от него избавиться.
Шаг 4. Итерация + памятка (снизу вверх)
def rob(nums,n):
if (n == 0):
return 0
memo = [0]*(n+1)
memo[1] = nums[0]
for i in range(n):
val = nums[i]
memo[i+1] = max(memo[i], memo[i-1] + val)
return memo[n]
Шаг 5. Итерация + 2 переменные (снизу вверх)
Мы можем заметить, что на предыдущем шаге мы использовали только memo[i]
и memo[i-1]
, поэтому возвращаемся всего на 2 шага назад. Вместо этого мы можем хранить их в двух переменных. Эта оптимизация встречается при построении последовательности Фибоначчи и некоторых других задачах [вставить ссылки].
def rob(nums,n):
if (n == 0):
return 0
prev1 = 0
prev2 = 0
for num in nums:
tmp = prev1
prev1 = max(prev2 + num, prev1)
prev2 = tmp
return prev1
Есть несколько шаблонов, которые можно найти в разных задачах. Эти шаблоны могут быть полезны при решении DP.
Узоры
Минимальный (максимальный) путь достижения цели
Отдельные пути
Объединяющие интервалы
ДП на строках
Принятие решений
Минимальный (максимальный) путь достижения цели
Список проблем: https://leetcode.com/list/55ac4kuc
Создать постановку задачи для этого шаблона
Заявление
Для заданной цели найдите минимальные (максимальные) затраты/путь/сумму для достижения цели.
Подход
Выберите минимальный (максимальный) путь из всех возможных путей до текущего состояния, затем добавьте значение для текущего состояния.
routes[i] = min(routes[i-1], routes[i-2], ... , routes[i-k]) + cost[i]
Сгенерируйте оптимальные решения для всех значений в целевом объекте и верните значение для целевого объекта.
Низходящий
for j in range(n):
result = min(result, topDown(target - ways[j]) + cost/ path / sum)
return memo[/*state parameters*/] = result;
Вверх дном
for i in range(1,target+1):
for j in range(len(ways)):
if (ways[j] <= i):
dp[i] = min(dp[i], dp[i - ways[j]] + cost / path / sum)
return dp[target]
Похожие проблемы
746. Минимальная стоимость подъема по лестнице Easy
Низходящий
result = min(minCost(n-1, cost, memo), minCost(n-2, cost, memo)) + (n == cost.size() ? 0 : cost[n])
return memo[n] = result
Вверх дном
for i in range(2,n+1):
dp[i] = min(dp[i-1], dp[i-2]) + (i == n ? 0 : cost[i])
return dp[n]
64. Минимальная сумма пути Medium
Низходящий
result = min(pathSum(i+1, j, grid, memo), pathSum(i, j+1, grid, memo)) + grid[i][j]
return memo[i][j] = result
Вверх дном
for i in range(1,n):
for j in range(1,m):
grid[i][j] = min(grid[i-1][j], grid[i][j-1]) + grid[i][j]
return grid[n-1][m-1]
322. Размен монет Medium
Низходящий
for i in range(len(coins)):
if (coins[i] <= target):// check validity of a sub-problem
result = min(ans, CoinChange(target - coins[i], coins) + 1)
return memo[target] = result
Вверх дном
for j in range(1,amount+1):
for i in range(len(coins)):
if (coins[i] <= j):
dp[j] = min(dp[j], dp[j - coins[i]] + 1)
931. Минимальная сумма пути падения Medium
983. Минимальная стоимость билетов Medium
650. 2-клавишная клавиатура Medium
279. Идеальные квадраты Medium
1049. Последний каменный груз II Medium
120. Треугольник Medium
474. Единицы и нули Medium
221. Максимальная площадь Medium
322. Размен монет Medium
1240. Мозаика прямоугольника с наименьшим количеством квадратов Hard
174. Подземелье Hard
871. Минимальное количество остановок для заправки Hard
Отличные пути
Список проблем: https://leetcode.com/list/55ajm50i
Создать постановку задачи для этого шаблона
Заявление
Для заданной цели найдите несколько различных способов ее достижения.
Подход
Суммируйте все возможные способы достижения текущего состояния.
routes[i] = routes[i-1] + routes[i-2], ... , + routes[i-k]
Сгенерируйте сумму для всех значений в цели и верните значение для цели.
Низходящий
for j in range(len(ways)):
result += topDown(target - ways[j])
return memo[/*state parameters*/] = result
Вверх дном
for i in range(1,target+1):
for j in range(len(ways)):
if (ways[j] <= i):
dp[i] += dp[i - ways[j]]
return dp[target]
Похожие проблемы
70. Восхождение по лестнице Easy
Низходящий
result = climbStairs(n-1, memo) + climbStairs(n-2, memo)
return memo[n] = result
Вверх дном
for stair in range(2,n+1):
for step in range(1,3):
dp[stair] += dp[stair-step]
62. Уникальные пути Medium
Низходящий
result = UniquePaths(x-1, y) + UniquePaths(x, y-1)
return memo[x][y] = result
Вверх дном
for i in range(1,m):
for j in range(1,n):
dp[i][j] = dp[i][j-1] + dp[i-1][j]
1155. Количество бросков кубиков с заданной суммой Medium
for rep in range(1,d+1):
new_ways=[0]*(target+1)
for already in range(target+1):
for pipe in range(1,f+1):
if (already - pipe >= 0):
new_ways[already] += ways[already - pipe]
new_ways[already] %= mod
ways = new_ways
Примечание
В некоторых вопросах указывается количество повторений, в этом случае добавьте еще один цикл, чтобы имитировать каждое повторение.
688. Вероятность коня на шахматной доске Medium
494. Целевая сумма Medium
377. Комбинация Сумма IV Medium
935. Рыцарь Звонилка Medium
1223. Моделирование броска игральных костей Medium
416. Сумма равного подмножества разделов Medium
808. Порции супа Medium
790. Домино и Тромино Тайлинг Medium
801. Минимальные свопы для увеличения последовательности
673. Номер самой длинной возрастающей подпоследовательности Medium
63. Уникальные пути II Medium
576. Вне пограничных путей Medium
1269. Количество способов остаться на том же месте после нескольких шагов Hard
1220. Подсчитайте перестановку гласных Hard
Объединение интервалов
Список проблем: https://leetcode.com/list/55aj8s16
Создать постановку задачи для этого шаблона
Заявление
По заданному набору чисел найдите оптимальное решение задачи, учитывая текущее число и лучшее, что вы можете получить с левой и правой сторон.
Подход
Найти все оптимальные решения для каждого интервала и вернуть наилучший возможный ответ.
// from i to j
dp[i][j] = dp[i][k] + result[k] + dp[k+1][j]
Получите лучшее из левой и правой сторон и добавьте решение для текущей позиции.
for l in range(1,n):
for i in range(n-l):
j = i+l
for k in range(i,j):
dp[i][j] = max(dp[i][j], dp[i][k] + result[k] + dp[k+1][j])
return dp[0][n-1]
Похожие проблемы
1130. Дерево минимальной стоимости из конечных значений Medium
for l in range(1,n):
for i in range(n-l):
j = i + l
dp[i][j] = sys.maxsize
for k in range(i,j):
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + maxs[i][k] * maxs[k+1][j])
96. Уникальные бинарные деревья поиска Medium
1039. Минимальная оценка триангуляции полигона Medium
546. Удалить коробки Medium
1000. Минимальная стоимость объединения камней Medium
312. Лопающиеся шары Hard
375. Угадай число больше или меньше II Medium
ДП на струнах
Список проблем: https://leetcode.com/list/55afh7m7
Общая постановка проблемы для этого шаблона может варьироваться, но в большинстве случаев вам даются две строки, длина которых невелика.
Заявление
По двум строкам
s1
иs2
вернутьsome result
.
Подход
Большинство задач в этом шаблоне требуют решения, приемлемого по сложности O(n²).
// i - indexing string s1
// j - indexing string s2
for i in range(1,n+1):
for j in range(1,m+1):
if (s1[i-1] == s2[j-1]):
dp[i][j] = /*code*/;
else:
dp[i][j] = /*code*/;
Если вам дана одна строка
s
, подход может незначительно отличаться
for l in range(1,n):
for i in range(n-l):
j = i + l
if (s[i] == s[j]):
dp[i][j] = /*code*/;
else:
dp[i][j] = /*code*/;
1143. Самая длинная общая подпоследовательность Medium
for i in range(1,n+1):
for j in range(1,m+1):
if (text1[i-1] == text2[j-1]):
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
647. Палиндромные подстроки Medium
for l in range(1,n):
for i in range(n-l):
j = i + l
if (s[i] == s[j] && dp[i+1][j-1] == j-i-1):
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = 0;
516. Самая длинная палиндромная последовательность Medium
1092. Кратчайшая общая суперпоследовательность Medium
72. Редактировать расстояние Hard
115. Отдельные подпоследовательности Hard
712. Минимальная сумма удаления ASCII для двух строк Medium
5. Самая длинная палиндромная подстрока Medium
Принятие решения
Список проблем: https://leetcode.com/list/55af7bu7
Общая постановка проблемы для этого шаблона заключается в том, чтобы принять решение о прощенной ситуации, использовать или не использовать текущее состояние. Итак, задача требует от вас принятия решения в текущем состоянии.
Заявление
По заданному набору значений найдите ответ с возможностью выбора или игнорирования текущего значения.
Подход
Если вы решите выбрать текущее значение, используйте предыдущий результат, в котором значение было проигнорировано; и наоборот, если вы решите игнорировать текущее значение, используйте предыдущий результат, в котором использовалось значение.
// i - indexing a set of values
// j - options to ignore j values
for i in range(1,n):
for j in range(1,k+1):
dp[i][j] = max(dp[i][j], dp[i-1][j] + arr[i], dp[i-1][j-1])
dp[i][j-1] = max(dp[i][j-1], dp[i-1][j-1] + arr[i], arr[i])
198. Грабитель домов Easy
for i in range(1,n):
dp[i][1] = max(dp[i-1][0] + nums[i], dp[i-1][1])
dp[i][0] = dp[i-1][1]
121. Лучшее время для покупки и продажи акций Easy
714. Лучшее время для покупки и продажи акций с комиссией за транзакцию Medium
309. Лучшее время для покупки и продажи акций с кулдауном Medium
123. Лучшее время для покупки и продажи акций III Hard
188. Лучшее время для покупки и продажи акций IV Hard
Надеюсь, эти советы будут полезны 😊