This commit is contained in:
krahets
2026-03-30 08:17:41 +08:00
parent 68cafe99dd
commit 46bccf0065
484 changed files with 60193 additions and 20315 deletions
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3743,7 +3743,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4355,11 +4355,11 @@
<!-- Page content -->
<h1 id="142">14.2 &nbsp; Свойства задач динамического программирования<a class="headerlink" href="#142" title="Permanent link">&para;</a></h1>
<p>В предыдущем разделе мы увидели, как динамическое программирование решает исходную задачу через разложение на подзадачи. На самом деле разложение на подзадачи - это общий алгоритмический подход, но в divide and conquer, динамическом программировании и backtracking акценты расставлены по-разному.</p>
<p>В предыдущем разделе мы увидели, как динамическое программирование решает исходную задачу через разложение на подзадачи. На самом деле разложение на подзадачи - это общий алгоритмический подход, но в методе "разделяй и властвуй", динамическом программировании и поиске с возвратом акценты расставлены по-разному.</p>
<ul>
<li>Алгоритмы divide and conquer рекурсивно раскладывают исходную задачу на несколько независимых подзадач, пока не будет достигнута наименьшая подзадача, а затем в процессе возврата объединяют решения подзадач в решение исходной задачи.</li>
<li>Динамическое программирование тоже раскладывает задачу рекурсивно, но его главное отличие от divide and conquer в том, что подзадачи здесь зависят друг от друга и в процессе разложения возникает много перекрывающихся подзадач.</li>
<li>Алгоритм backtracking перебирает все возможные решения через попытки и откат и с помощью обрезки избегает ненужных ветвей поиска. Решение исходной задачи состоит из последовательности решений, и подзадачей можно считать префикс этой последовательности решений.</li>
<li>Алгоритмы "разделяй и властвуй" рекурсивно раскладывают исходную задачу на несколько независимых подзадач, пока не будет достигнута наименьшая подзадача, а затем в процессе возврата объединяют решения подзадач в решение исходной задачи.</li>
<li>Динамическое программирование тоже раскладывает задачу рекурсивно, но его главное отличие от метода "разделяй и властвуй" в том, что подзадачи здесь зависят друг от друга и в процессе разложения возникает много перекрывающихся подзадач.</li>
<li>Алгоритм поиска с возвратом перебирает все возможные решения через попытки и откат и с помощью обрезки избегает ненужных ветвей поиска. Решение исходной задачи состоит из последовательности решений, и подзадачей можно считать префикс этой последовательности решений.</li>
</ul>
<p>На практике динамическое программирование часто применяется для задач оптимизации. Такие задачи не только содержат перекрывающиеся подзадачи, но и обладают еще двумя важными свойствами: оптимальной подструктурой и отсутствием последствий.</p>
<h2 id="1421">14.2.1 &nbsp; Оптимальная подструктура<a class="headerlink" href="#1421" title="Permanent link">&para;</a></h2>
@@ -5235,7 +5235,7 @@ aria-label="Нижний колонтитул"
<a
href="../intro_to_dynamic_programming/"
class="md-footer__link md-footer__link--prev"
aria-label="Назад: 14.1 Введение в динамическое программирование"
aria-label="Назад: 14.1 Первое знакомство с динамическим программированием"
rel="prev"
>
<div class="md-footer__button md-icon">
@@ -5247,7 +5247,7 @@ aria-label="Нижний колонтитул"
Назад
</span>
<div class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
</div>
</div>
</a>
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3793,7 +3793,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4461,9 +4461,9 @@
<li>С чего начинать решение такой задачи и как выглядит полный процесс решения?</li>
</ol>
<h2 id="1431">14.3.1 &nbsp; Определение задачи<a class="headerlink" href="#1431" title="Permanent link">&para;</a></h2>
<p>В целом, если задача содержит перекрывающиеся подзадачи, оптимальную подструктуру и удовлетворяет свойству отсутствия последствий, то она обычно подходит для решения с помощью динамического программирования. Однако извлечь все эти свойства напрямую из формулировки задачи бывает трудно. Поэтому на практике мы обычно ослабляем требования и <strong>сначала смотрим, подходит ли задача для решения методом backtracking (полного перебора)</strong>.</p>
<p><strong>Задачи, подходящие для backtracking, обычно удовлетворяют "модели дерева решений"</strong>. Такие задачи можно описать деревом, где каждый узел представляет одно решение, а каждый путь представляет последовательность решений.</p>
<p>Иначе говоря, если в задаче есть четко выраженные решения и ответ порождается последовательностью таких решений, то она удовлетворяет модели дерева решений и обычно допускает решение через backtracking.</p>
<p>В целом, если задача содержит перекрывающиеся подзадачи, оптимальную подструктуру и удовлетворяет свойству отсутствия последствий, то она обычно подходит для решения с помощью динамического программирования. Однако извлечь все эти свойства напрямую из формулировки задачи бывает трудно. Поэтому на практике мы обычно ослабляем требования и <strong>сначала смотрим, подходит ли задача для решения методом поиска с возвратом (полного перебора)</strong>.</p>
<p><strong>Задачи, подходящие для поиска с возвратом, обычно удовлетворяют "модели дерева решений"</strong>. Такие задачи можно описать деревом, где каждый узел представляет одно решение, а каждый путь представляет последовательность решений.</p>
<p>Иначе говоря, если в задаче есть четко выраженные решения и ответ порождается последовательностью таких решений, то она удовлетворяет модели дерева решений и обычно допускает решение через поиск с возвратом.</p>
<p>Поверх этого у задач динамического программирования есть и некоторые дополнительные "плюсы".</p>
<ul>
<li>В условии задачи фигурируют слова "максимальный", "минимальный", "наибольший", "наименьший" и другие формулировки оптимизации.</li>
@@ -4495,7 +4495,7 @@
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Как в динамическом программировании, так и в backtracking, решение задачи можно описать как последовательность решений, а состояние образуется всеми переменными решений. Оно должно содержать всю информацию, достаточную для вывода следующего состояния.</p>
<p>Как в динамическом программировании, так и в поиске с возвратом, решение задачи можно описать как последовательность решений, а состояние образуется всеми переменными решений. Оно должно содержать всю информацию, достаточную для вывода следующего состояния.</p>
<p>Каждому состоянию соответствует некоторая подзадача, и для хранения решений всех подзадач мы определяем таблицу <span class="arithmatex">\(dp\)</span> ; каждая независимая переменная состояния становится одним измерением таблицы <span class="arithmatex">\(dp\)</span> . По сути таблица <span class="arithmatex">\(dp\)</span> - это отображение от состояния к решению соответствующей подзадачи.</p>
</div>
<p><strong>Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния</strong></p>
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3673,7 +3673,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4377,7 +4377,7 @@
<!-- Page content -->
<h1 id="146">14.6 &nbsp; Задача о расстоянии редактирования<a class="headerlink" href="#146" title="Permanent link">&para;</a></h1>
<p>Расстояние редактирования, также называемое расстоянием Левенштейна, обозначает минимальное число правок, необходимых для взаимного преобразования двух строк. Обычно оно используется для измерения сходства двух последовательностей в информационном поиске и обработке естественного языка.</p>
<p>Расстояние редактирования, также называемое расстоянием Левенштейна, - это минимальное количество изменений, необходимых для преобразования одной строки в другую. Обычно оно используется для измерения сходства двух последовательностей в информационном поиске и обработке естественного языка.</p>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Даны две строки <span class="arithmatex">\(s\)</span> и <span class="arithmatex">\(t\)</span> . Верните минимальное число шагов редактирования, необходимое для преобразования <span class="arithmatex">\(s\)</span> в <span class="arithmatex">\(t\)</span> .</p>
@@ -4387,7 +4387,7 @@
<p><img alt="Пример данных для задачи о расстоянии редактирования" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_example.png" /></p>
<p align="center"> Рисунок 14-27 &nbsp; Пример данных для задачи о расстоянии редактирования </p>
<p><strong>Задачу о расстоянии редактирования можно очень естественно описать через модель дерева решений</strong>. Строки соответствуют узлам дерева, а один раунд решения (одна операция редактирования) соответствует одному ребру дерева.</p>
<p><strong>Задачу о расстоянии редактирования можно естественным образом объяснить с помощью модели дерева решений</strong>. Строки соответствуют узлам дерева, а один шаг решения, то есть одна операция редактирования, соответствует одному ребру дерева.</p>
<p>Как показано на рисунке 14-28, если не ограничивать число операций, то каждый узел может порождать множество ребер, и каждое из них соответствует одному из вариантов преобразования. Это означает, что преобразовать <code>hello</code> в <code>algo</code> можно множеством разных путей.</p>
<p>С точки зрения дерева решений цель этой задачи - найти кратчайший путь между узлом <code>hello</code> и узлом <code>algo</code> .</p>
<p><img alt="Представление задачи о расстоянии редактирования через дерево решений" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_decision_tree.png" /></p>
@@ -4401,7 +4401,7 @@
<li>Если <span class="arithmatex">\(s[n-1]\)</span> и <span class="arithmatex">\(t[m-1]\)</span> совпадают, их можно просто пропустить и сразу перейти к сравнению <span class="arithmatex">\(s[n-2]\)</span> и <span class="arithmatex">\(t[m-2]\)</span> .</li>
<li>Если <span class="arithmatex">\(s[n-1]\)</span> и <span class="arithmatex">\(t[m-1]\)</span> различны, нужно выполнить над <span class="arithmatex">\(s\)</span> одну операцию редактирования (вставку, удаление или замену), чтобы последние символы стали одинаковыми, после чего можно перейти к задаче меньшего размера.</li>
</ul>
<p>Иначе говоря, каждое решение (операция редактирования), которое мы выполняем над строкой <span class="arithmatex">\(s\)</span> , меняет те символы, которые еще остаются несопоставленными в строках <span class="arithmatex">\(s\)</span> и <span class="arithmatex">\(t\)</span> . Поэтому состояние определяется текущими позициями рассматриваемых символов в <span class="arithmatex">\(s\)</span> и <span class="arithmatex">\(t\)</span> , то есть состоянием <span class="arithmatex">\([i, j]\)</span> .</p>
<p>Иначе говоря, каждый шаг решения, то есть операция редактирования над строкой <span class="arithmatex">\(s\)</span> , меняет те символы, которые еще необходимо сопоставить в строках <span class="arithmatex">\(s\)</span> и <span class="arithmatex">\(t\)</span> . Поэтому состояние определяется текущими позициями рассматриваемых символов в <span class="arithmatex">\(s\)</span> и <span class="arithmatex">\(t\)</span> , то есть состоянием <span class="arithmatex">\([i, j]\)</span> .</p>
<p>Подзадача, соответствующая состоянию <span class="arithmatex">\([i, j]\)</span> , такова: <strong>минимальное число операций редактирования, необходимое для преобразования первых <span class="arithmatex">\(i\)</span> символов строки <span class="arithmatex">\(s\)</span> в первые <span class="arithmatex">\(j\)</span> символов строки <span class="arithmatex">\(t\)</span></strong>.</p>
<p>Отсюда получается двумерная таблица <span class="arithmatex">\(dp\)</span> размера <span class="arithmatex">\((i+1) \times (j+1)\)</span> .</p>
<p><strong>Шаг 2: найти оптимальную подструктуру и на ее основе вывести уравнение перехода состояния</strong></p>
@@ -4414,7 +4414,7 @@
<p><img alt="Переходы состояния в задаче о расстоянии редактирования" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_state_transfer.png" /></p>
<p align="center"> Рисунок 14-29 &nbsp; Переходы состояния в задаче о расстоянии редактирования </p>
<p>Согласно этому анализу оптимальная подструктура такова: минимальное число шагов редактирования для <span class="arithmatex">\(dp[i, j]\)</span> равно минимуму из трех значений - <span class="arithmatex">\(dp[i, j-1]\)</span> , <span class="arithmatex">\(dp[i-1, j]\)</span> и <span class="arithmatex">\(dp[i-1, j-1]\)</span> - плюс цена текущей операции редактирования <span class="arithmatex">\(1\)</span> . Значит, уравнение перехода состояния имеет вид:</p>
<p>Согласно этому анализу оптимальная подструктура такова: минимальное число шагов редактирования для <span class="arithmatex">\(dp[i, j]\)</span> равно минимуму из трех значений - <span class="arithmatex">\(dp[i, j-1]\)</span> , <span class="arithmatex">\(dp[i-1, j]\)</span> и <span class="arithmatex">\(dp[i-1, j-1]\)</span> - плюс <span class="arithmatex">\(1\)</span> шаг за текущее редактирование. Значит, уравнение перехода состояния имеет вид:</p>
<div class="arithmatex">\[
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
\]</div>
@@ -4809,7 +4809,7 @@ dp[i, j] = dp[i-1, j-1]
<p><div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5%20%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%3A%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BF%D0%B5%D1%80%D0%B2%D0%B0%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%20%D0%B8%20%D0%BF%D0%B5%D1%80%D0%B2%D1%8B%D0%B9%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D0%B5%D1%86%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D1%85%D0%BE%D0%B4%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B9%3A%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%BB%D0%B1%D1%86%D1%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%B2%D0%B0%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%2C%20%D1%81%D1%80%D0%B0%D0%B7%D1%83%20%D0%BF%D1%80%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%B8%D1%85%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B8%2C%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B8%20%D0%B7%D0%B0%D0%BC%D0%B5%D0%BD%D1%8B%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%D0%A7%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%7Bs%7D%20%D0%B2%20%7Bt%7D%2C%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%7Bres%7D%20%D1%88%D0%B0%D0%B3%D0%BE%D0%B2%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран &gt;</a></div></p>
</details>
<p>Как показано на рисунке 14-30, процесс переходов состояния в задаче о расстоянии редактирования очень похож на процесс в задачах о рюкзаке: в обоих случаях это заполнение двумерной сетки.</p>
<p>Как показано на рисунке 14-30, процесс переходов состояния в задаче о расстоянии редактирования очень похож на задачи о рюкзаке: и там и здесь его можно рассматривать как заполнение двумерной сетки.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:15"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><input id="__tabbed_2_14" name="__tabbed_2" type="radio" /><input id="__tabbed_2_15" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">&lt;1&gt;</label><label for="__tabbed_2_2">&lt;2&gt;</label><label for="__tabbed_2_3">&lt;3&gt;</label><label for="__tabbed_2_4">&lt;4&gt;</label><label for="__tabbed_2_5">&lt;5&gt;</label><label for="__tabbed_2_6">&lt;6&gt;</label><label for="__tabbed_2_7">&lt;7&gt;</label><label for="__tabbed_2_8">&lt;8&gt;</label><label for="__tabbed_2_9">&lt;9&gt;</label><label for="__tabbed_2_10">&lt;10&gt;</label><label for="__tabbed_2_11">&lt;11&gt;</label><label for="__tabbed_2_12">&lt;12&gt;</label><label for="__tabbed_2_13">&lt;13&gt;</label><label for="__tabbed_2_14">&lt;14&gt;</label><label for="__tabbed_2_15">&lt;15&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4863,7 +4863,7 @@ dp[i, j] = dp[i-1, j-1]
<h3 id="3">3. &nbsp; Оптимизация пространства<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>Поскольку <span class="arithmatex">\(dp[i,j]\)</span> зависит от значения сверху <span class="arithmatex">\(dp[i-1, j]\)</span> , слева <span class="arithmatex">\(dp[i, j-1]\)</span> и слева сверху <span class="arithmatex">\(dp[i-1, j-1]\)</span> , прямой обход после оптимизации памяти теряет значение слева сверху, а обратный обход не позволяет заранее построить значение слева <span class="arithmatex">\(dp[i, j-1]\)</span> . Значит, оба наивных варианта обхода здесь непригодны.</p>
<p>Чтобы решить эту проблему, можно использовать переменную <code>leftup</code> для временного сохранения значения слева сверху <span class="arithmatex">\(dp[i-1, j-1]\)</span> ; после этого остается учитывать только верхнее и левое значения. Тогда ситуация становится эквивалентной задаче о полном рюкзаке, и можно выполнять прямой обход. Код приведен ниже:</p>
<p>Чтобы решить эту проблему, можно использовать переменную <code>leftup</code> для временного сохранения значения слева сверху <span class="arithmatex">\(dp[i-1, j-1]\)</span> ; после этого остается учитывать только верхнее и левое значения. Тогда ситуация становится аналогичной задаче о полном рюкзаке, и можно выполнять прямой обход. Код приведен ниже:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:13"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><input id="__tabbed_3_12" name="__tabbed_3" type="radio" /><input id="__tabbed_3_13" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Python</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Java</label><label for="__tabbed_3_4">C#</label><label for="__tabbed_3_5">Go</label><label for="__tabbed_3_6">Swift</label><label for="__tabbed_3_7">JS</label><label for="__tabbed_3_8">TS</label><label for="__tabbed_3_9">Dart</label><label for="__tabbed_3_10">Rust</label><label for="__tabbed_3_11">C</label><label for="__tabbed_3_12">Kotlin</label><label for="__tabbed_3_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -5298,7 +5298,7 @@ aria-label="Нижний колонтитул"
<a
href="../unbounded_knapsack_problem/"
class="md-footer__link md-footer__link--prev"
aria-label="Назад: 14.5 Задача о неограниченном рюкзаке"
aria-label="Назад: 14.5 Задача о полном рюкзаке"
rel="prev"
>
<div class="md-footer__button md-icon">
@@ -5310,7 +5310,7 @@ aria-label="Нижний колонтитул"
Назад
</span>
<div class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
</div>
</div>
</a>
+35 -35
View File
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3673,7 +3673,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4282,11 +4282,11 @@
</div>
<h2 id="_1">Содержание главы<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="intro_to_dynamic_programming/">14.1 &nbsp; Введение в динамическое программирование</a></li>
<li><a href="intro_to_dynamic_programming/">14.1 &nbsp; Первое знакомство с динамическим программированием</a></li>
<li><a href="dp_problem_features/">14.2 &nbsp; Свойства задач динамического программирования</a></li>
<li><a href="dp_solution_pipeline/">14.3 &nbsp; Подход к решению задач динамического программирования</a></li>
<li><a href="knapsack_problem/">14.4 &nbsp; Задача о рюкзаке 0-1</a></li>
<li><a href="unbounded_knapsack_problem/">14.5 &nbsp; Задача о неограниченном рюкзаке</a></li>
<li><a href="unbounded_knapsack_problem/">14.5 &nbsp; Задача о полном рюкзаке</a></li>
<li><a href="edit_distance_problem/">14.6 &nbsp; Задача о расстоянии редактирования</a></li>
<li><a href="summary/">14.7 &nbsp; Резюме</a></li>
</ul>
@@ -4336,7 +4336,7 @@ aria-label="Нижний колонтитул"
<a
href="./intro_to_dynamic_programming/"
class="md-footer__link md-footer__link--next"
aria-label="Вперед: 14.1 Введение в динамическое программирование"
aria-label="Вперед: 14.1 Первое знакомство с динамическим программированием"
rel="next"
>
<div class="md-footer__title">
@@ -4344,7 +4344,7 @@ aria-label="Нижний колонтитул"
Вперед
</span>
<div class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
</div>
</div>
<div class="md-footer__button md-icon">
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3570,7 +3570,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3588,7 +3588,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3765,7 +3765,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4400,7 +4400,7 @@
<!-- Page content -->
<h1 id="141">14.1 &nbsp; Первое знакомство с динамическим программированием<a class="headerlink" href="#141" title="Permanent link">&para;</a></h1>
<p><u>Динамическое программирование (dynamic programming)</u> - это важная алгоритмическая парадигма, которая разбивает задачу на последовательность более мелких подзадач и за счет хранения их решений избегает повторных вычислений, тем самым резко повышая эффективность по времени.</p>
<p>В этом разделе мы начнем с классического примера: сначала запишем его грубое решение через backtracking, увидим в нем перекрывающиеся подзадачи, а затем постепенно выведем более эффективное решение на основе динамического программирования.</p>
<p>В этом разделе мы начнем с классического примера: сначала представим его грубое решение методом поиска с возвратом, увидим в нем перекрывающиеся подзадачи, а затем постепенно выведем более эффективное решение на основе динамического программирования.</p>
<div class="admonition question">
<p class="admonition-title">Подъем по лестнице</p>
<p>Дана лестница из <span class="arithmatex">\(n\)</span> ступеней. За один шаг можно подняться на <span class="arithmatex">\(1\)</span> или на <span class="arithmatex">\(2\)</span> ступени. Сколькими способами можно добраться до вершины?</p>
@@ -4409,7 +4409,7 @@
<p><img alt="Число способов подняться на 3-ю ступень" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_example.png" /></p>
<p align="center"> Рисунок 14-1 &nbsp; Число способов подняться на 3-ю ступень </p>
<p>Цель этой задачи - вычислить количество способов. <strong>Поэтому можно попробовать грубо перебрать все варианты с помощью backtracking</strong>. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на <span class="arithmatex">\(1\)</span> или на <span class="arithmatex">\(2\)</span> ступени; всякий раз, когда достигаем вершины, увеличиваем число способов на <span class="arithmatex">\(1\)</span> , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так:</p>
<p>Цель этой задачи - вычислить количество способов. <strong>Поэтому можно попробовать использовать для ее решения метод поиска с возвратом</strong>. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на <span class="arithmatex">\(1\)</span> или на <span class="arithmatex">\(2\)</span> ступени; всякий раз, когда достигаем вершины, увеличиваем число способов на <span class="arithmatex">\(1\)</span> , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4794,7 +4794,7 @@
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%91%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%B4%D0%BE%D1%81%D1%82%D0%B8%D0%B3%D0%B0%D0%B5%D1%82%20n-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%2C%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D1%83%D0%B2%D0%B5%D0%BB%D0%B8%D1%87%D0%B8%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F%20%D0%BD%D0%B0%201%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B1%D0%BE%D1%80%20%D0%B2%D1%81%D0%B5%D1%85%20%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82%D0%BE%D0%B2%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%B0%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%3A%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%B2%D1%8B%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C%20%D0%B7%D0%B0%20n-%D1%8E%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D1%8C%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BF%D1%8B%D1%82%D0%BA%D0%B0%3A%20%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9F%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%3A%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%20%D0%9C%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BD%D0%B0%201%20%D0%B8%D0%BB%D0%B8%202%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20state%20%3D%200%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%D0%B4%D1%8A%D0%B5%D0%BC%20%D1%81%200-%D0%B9%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B8%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20res%5B0%5D%20%D0%B4%D0%BB%D1%8F%20%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%87%D0%B8%D1%81%D0%BB%D0%B0%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%D0%9A%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%BE%D0%B2%20%D0%BF%D0%BE%D0%B4%D0%BD%D1%8F%D1%82%D1%8C%D1%81%D1%8F%20%D0%BF%D0%BE%20%D0%BB%D0%B5%D1%81%D1%82%D0%BD%D0%B8%D1%86%D0%B5%20%D0%B8%D0%B7%20%7Bn%7D%20%D1%81%D1%82%D1%83%D0%BF%D0%B5%D0%BD%D0%B5%D0%B9%20%3D%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран &gt;</a></div></p>
</details>
<h2 id="1411-1">14.1.1 &nbsp; Метод 1: полный перебор<a class="headerlink" href="#1411-1" title="Permanent link">&para;</a></h2>
<p>Backtracking обычно не раскладывает задачу явно на подзадачи; вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов.</p>
<p>Алгоритм поиска с возвратом обычно не раскладывает задачу явно на подзадачи; вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов.</p>
<p>Попробуем посмотреть на задачу именно как на разложение подзадач. Пусть число способов добраться до ступени <span class="arithmatex">\(i\)</span> равно <span class="arithmatex">\(dp[i]\)</span> ; тогда <span class="arithmatex">\(dp[i]\)</span> - это исходная задача, а ее подзадачи включают:</p>
<div class="arithmatex">\[
dp[i-1], dp[i-2], \dots, dp[2], dp[1]
@@ -4809,7 +4809,7 @@ dp[i] = dp[i-1] + dp[i-2]
<p align="center"> Рисунок 14-2 &nbsp; Рекуррентная связь числа способов </p>
<p>По рекуррентной формуле можно получить решение полного перебора. Начиная с <span class="arithmatex">\(dp[n]\)</span> , <strong>мы рекурсивно разлагаем большую задачу в сумму двух меньших задач</strong> , пока не дойдем до наименьших подзадач <span class="arithmatex">\(dp[1]\)</span> и <span class="arithmatex">\(dp[2]\)</span> . Их решения уже известны: <span class="arithmatex">\(dp[1] = 1\)</span> и <span class="arithmatex">\(dp[2] = 2\)</span> , что означает <span class="arithmatex">\(1\)</span> и <span class="arithmatex">\(2\)</span> способа подняться соответственно на <span class="arithmatex">\(1\)</span>-ю и <span class="arithmatex">\(2\)</span>-ю ступени.</p>
<p>Посмотрите на следующий код: как и стандартный backtracking, он относится к поиску в глубину, но выглядит более компактно:</p>
<p>Посмотрите на следующий код: как и стандартный поиск с возвратом, он относится к поиску в глубину, но выглядит более компактно:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:13"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -5626,7 +5626,7 @@ dp[i] = dp[i-1] + dp[i-2]
<p><img alt="Процесс динамического программирования для подъема по лестнице" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_dp.png" /></p>
<p align="center"> Рисунок 14-5 &nbsp; Процесс динамического программирования для подъема по лестнице </p>
<p>Как и в backtracking, в динамическом программировании используется понятие "состояние" для обозначения некоторого этапа решения задачи; каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени <span class="arithmatex">\(i\)</span> .</p>
<p>Как и в поиске с возвратом, в динамическом программировании используется понятие "состояние" для обозначения некоторого этапа решения задачи; каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени <span class="arithmatex">\(i\)</span> .</p>
<p>На основе сказанного можно подвести несколько часто используемых терминов динамического программирования.</p>
<ul>
<li>Массив <code>dp</code> называют <u>таблицей dp</u>, а <span class="arithmatex">\(dp[i]\)</span> обозначает решение подзадачи, соответствующей состоянию <span class="arithmatex">\(i\)</span> .</li>
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3713,7 +3713,7 @@
<a href="#2-2" class="md-nav__link">
<span class="md-ellipsis">
2. &nbsp; Метод 2: поиск с мемоизацией
2. &nbsp; Метод 2: мемоизация
</span>
</a>
@@ -3765,7 +3765,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4332,7 +4332,7 @@
<a href="#2-2" class="md-nav__link">
<span class="md-ellipsis">
2. &nbsp; Метод 2: поиск с мемоизацией
2. &nbsp; Метод 2: мемоизация
</span>
</a>
@@ -4399,7 +4399,7 @@
<!-- Page content -->
<h1 id="144-0-1">14.4 &nbsp; Задача о рюкзаке 0-1<a class="headerlink" href="#144-0-1" title="Permanent link">&para;</a></h1>
<p>Задача о рюкзаке - это очень хороший вводный пример для динамического программирования и одна из самых типичных форм задач этого класса. У нее существует множество вариантов, например задача о рюкзаке 0-1, задача о полном рюкзаке, задача о многократном рюкзаке и т.д.</p>
<p>Задача о рюкзаке является отличным примером для начала изучения динамического программирования и представляет собой одну из наиболее распространенных форм этой задачи. У нее существует множество вариантов, например задача о рюкзаке 0-1, задача о полном рюкзаке, задача о многократном рюкзаке и т.д.</p>
<p>В этом разделе сначала разберем самый распространенный вариант - задачу о рюкзаке 0-1.</p>
<div class="admonition question">
<p class="admonition-title">Question</p>
@@ -4409,8 +4409,8 @@
<p><img alt="Пример данных для задачи о рюкзаке 0-1" class="animation-figure" src="../knapsack_problem.assets/knapsack_example.png" /></p>
<p align="center"> Рисунок 14-17 &nbsp; Пример данных для задачи о рюкзаке 0-1 </p>
<p>На задачу о рюкзаке 0-1 можно смотреть как на процесс из <span class="arithmatex">\(n\)</span> раундов решений: для каждого предмета есть два решения - не класть его в рюкзак или положить в рюкзак. Поэтому задача удовлетворяет модели дерева решений.</p>
<p>Цель задачи - найти "максимальную суммарную стоимость при ограниченной вместимости рюкзака", значит, с большой вероятностью это задача динамического программирования.</p>
<p>Задачу о рюкзаке 0-1 можно рассматривать как процесс из <span class="arithmatex">\(n\)</span> раундов принятия решений: для каждого предмета есть два решения - не класть его в рюкзак или положить в рюкзак. Поэтому задача удовлетворяет модели дерева решений.</p>
<p>Цель задачи - найти "максимальную суммарную стоимость при ограниченной вместимости рюкзака", а это с большой вероятностью указывает на задачу динамического программирования.</p>
<p><strong>Шаг 1: продумать решения на каждом раунде, определить состояние и тем самым получить таблицу <span class="arithmatex">\(dp\)</span></strong></p>
<p>Для каждого предмета возможны два случая: не класть его в рюкзак, тогда вместимость не меняется; или положить его в рюкзак, тогда оставшаяся вместимость уменьшается. Отсюда получается определение состояния: текущий номер предмета <span class="arithmatex">\(i\)</span> и текущая вместимость рюкзака <span class="arithmatex">\(c\)</span> , то есть состояние обозначается как <span class="arithmatex">\([i, c]\)</span> .</p>
<p>Подзадача, соответствующая состоянию <span class="arithmatex">\([i, c]\)</span> , такова: <strong>максимальная стоимость, которую можно получить, используя первые <span class="arithmatex">\(i\)</span> предметов и рюкзак вместимости <span class="arithmatex">\(c\)</span></strong>. Ее решение обозначается через <span class="arithmatex">\(dp[i, c]\)</span> .</p>
@@ -4702,7 +4702,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
<p><img alt="Дерево полного перебора для задачи о рюкзаке 0-1" class="animation-figure" src="../knapsack_problem.assets/knapsack_dfs.png" /></p>
<p align="center"> Рисунок 14-18 &nbsp; Дерево полного перебора для задачи о рюкзаке 0-1 </p>
<h3 id="2-2">2. &nbsp; Метод 2: поиск с мемоизацией<a class="headerlink" href="#2-2" title="Permanent link">&para;</a></h3>
<h3 id="2-2">2. &nbsp; Метод 2: мемоизация<a class="headerlink" href="#2-2" title="Permanent link">&para;</a></h3>
<p>Чтобы каждая перекрывающаяся подзадача вычислялась только один раз, используем таблицу памяти <code>mem</code> для хранения решений подзадач, где <code>mem[i][c]</code> соответствует <span class="arithmatex">\(dp[i, c]\)</span> .</p>
<p>После введения мемоизации <strong>временная сложность определяется числом подзадач</strong> , то есть равна <span class="arithmatex">\(O(n \times cap)\)</span> . Код выглядит так:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:13"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label></div>
@@ -5412,13 +5412,13 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
<p align="center"> Рисунок 14-20 &nbsp; Процесс динамического программирования для задачи о рюкзаке 0-1 </p>
<h3 id="4">4. &nbsp; Оптимизация пространства<a class="headerlink" href="#4" title="Permanent link">&para;</a></h3>
<p>Поскольку каждое состояние зависит только от состояния в предыдущей строке, можно использовать два массива, которые будут "перекатываться" вперед, и тем самым уменьшить пространственную сложность с <span class="arithmatex">\(O(n^2)\)</span> до <span class="arithmatex">\(O(n)\)</span> .</p>
<p>Поскольку каждое состояние зависит только от состояния в предыдущей строке, можно использовать два массива, которые будут продвигаться вперед по очереди, и тем самым уменьшить пространственную сложность с <span class="arithmatex">\(O(n^2)\)</span> до <span class="arithmatex">\(O(n)\)</span> .</p>
<p>Если пойти дальше, можно спросить: можно ли оптимизировать память так, чтобы использовать только один массив? Наблюдение показывает, что каждое состояние зависит от клетки прямо сверху и клетки слева сверху. Предположим, что у нас есть только один массив, и в момент начала обхода строки <span class="arithmatex">\(i\)</span> он еще хранит состояния строки <span class="arithmatex">\(i-1\)</span> .</p>
<ul>
<li>Если обходить массив слева направо, то к моменту вычисления <span class="arithmatex">\(dp[i, j]\)</span> значения слева сверху <span class="arithmatex">\(dp[i-1, 1]\)</span> ~ <span class="arithmatex">\(dp[i-1, j-1]\)</span> могут уже быть перезаписаны, и правильный результат перехода состояния получить не удастся.</li>
<li>Если же обходить массив справа налево, проблема перезаписи не возникает, и переход состояния вычисляется корректно.</li>
</ul>
<p>На рисунке 14-21 показан процесс перехода от строки <span class="arithmatex">\(i = 1\)</span> к строке <span class="arithmatex">\(i = 2\)</span> при использовании одного массива. Попробуйте сопоставить его с разницей между прямым и обратным обходом.</p>
<p>На рисунке 14-21 показан процесс перехода от строки <span class="arithmatex">\(i = 1\)</span> к строке <span class="arithmatex">\(i = 2\)</span> при использовании одного массива. С его помощью удобно понять различие между прямым и обратным обходом.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:6"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">&lt;1&gt;</label><label for="__tabbed_5_2">&lt;2&gt;</label><label for="__tabbed_5_3">&lt;3&gt;</label><label for="__tabbed_5_4">&lt;4&gt;</label><label for="__tabbed_5_5">&lt;5&gt;</label><label for="__tabbed_5_6">&lt;6&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -5771,7 +5771,7 @@ aria-label="Нижний колонтитул"
<a
href="../unbounded_knapsack_problem/"
class="md-footer__link md-footer__link--next"
aria-label="Вперед: 14.5 Задача о неограниченном рюкзаке"
aria-label="Вперед: 14.5 Задача о полном рюкзаке"
rel="next"
>
<div class="md-footer__title">
@@ -5779,7 +5779,7 @@ aria-label="Нижний колонтитул"
Вперед
</span>
<div class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
</div>
</div>
<div class="md-footer__button md-icon">
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3673,7 +3673,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4336,9 +4336,9 @@
<h3 id="1">1. &nbsp; Ключевые выводы<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<ul>
<li>Динамическое программирование раскладывает задачу на подзадачи и повышает вычислительную эффективность за счет хранения решений этих подзадач и устранения повторных вычислений.</li>
<li>Если не учитывать затраты времени, то любую задачу динамического программирования можно решить с помощью backtracking (полного перебора), однако в дереве рекурсии возникает множество перекрывающихся подзадач, из-за чего эффективность крайне низка. После введения таблицы памяти можно хранить решения всех уже вычисленных подзадач и гарантировать, что каждая перекрывающаяся подзадача будет вычисляться только один раз.</li>
<li>Если не учитывать затраты времени, то любую задачу динамического программирования можно решить с помощью поиска с возвратом (полного перебора), однако в дереве рекурсии возникает множество перекрывающихся подзадач, из-за чего эффективность крайне низка. После введения таблицы памяти можно хранить решения всех уже вычисленных подзадач и гарантировать, что каждая перекрывающаяся подзадача будет вычисляться только один раз.</li>
<li>Поиск с мемоизацией - это рекурсивный метод "сверху вниз", а соответствующее ему динамическое программирование - это итеративный метод "снизу вверх", похожий на заполнение таблицы. Поскольку текущее состояние обычно зависит только от части локальных состояний, можно убрать одно измерение таблицы <span class="arithmatex">\(dp\)</span> и тем самым снизить пространственную сложность.</li>
<li>Разложение на подзадачи - это общий алгоритмический подход, но в divide and conquer, динамическом программировании и backtracking он имеет разные свойства.</li>
<li>Разложение на подзадачи - это общий алгоритмический подход, но в методе "разделяй и властвуй", динамическом программировании и поиске с возвратом он имеет разные свойства.</li>
<li>Для задач динамического программирования характерны три главных свойства: перекрывающиеся подзадачи, оптимальная подструктура и отсутствие последствий.</li>
<li>Если оптимальное решение исходной задачи можно построить из оптимальных решений подзадач, то задача обладает оптимальной подструктурой.</li>
<li>Отсутствие последствий означает, что для данного состояния его дальнейшее развитие определяется только этим состоянием и не зависит от всех прошлых состояний. Многие задачи комбинаторной оптимизации этим свойством не обладают и потому не могут эффективно решаться с помощью динамического программирования.</li>
@@ -65,8 +65,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Noto Sans";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"PT Sans";--md-code-font:"JetBrains Mono"}</style>
@@ -574,7 +574,7 @@
<span class="md-ellipsis">
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
@@ -596,7 +596,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 1. Знакомство с алгоритмами
Глава 1. Введение в алгоритмы
</label>
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 Что такое структуры данных и алгоритмы
1.2 Что такое алгоритм
@@ -1181,7 +1181,7 @@
<span class="md-ellipsis">
Глава 4. Массив и связный список
Глава 4. Массивы и списки
@@ -1203,7 +1203,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 4. Массив и связный список
Глава 4. Массивы и списки
</label>
@@ -1309,7 +1309,7 @@
<span class="md-ellipsis">
4.4 Память и кеш *
4.4 Оперативная память и кэш *
@@ -1591,7 +1591,7 @@
<span class="md-ellipsis">
Глава 6. Хеширование
Глава 6. Хеш-таблицы
@@ -1613,7 +1613,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 6. Хеширование
Глава 6. Хеш-таблицы
</label>
@@ -1691,7 +1691,7 @@
<span class="md-ellipsis">
6.3 Хеш-алгоритмы
6.3 Алгоритмы хеширования
@@ -1786,7 +1786,7 @@
<span class="md-ellipsis">
Глава 7. Дерево
Глава 7. Деревья
@@ -1808,7 +1808,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 7. Дерево
Глава 7. Деревья
</label>
@@ -1886,7 +1886,7 @@
<span class="md-ellipsis">
7.3 Представление дерева массивом
7.3 Представление двоичного дерева массивом
@@ -1970,7 +1970,7 @@
<span class="md-ellipsis">
7.6 Резюме
7.6 Краткие итоги
@@ -2133,7 +2133,7 @@
<span class="md-ellipsis">
8.3 Задача Top-K
8.3 Задача Top-k
@@ -2224,7 +2224,7 @@
<span class="md-ellipsis">
Глава 9. Граф
Глава 9. Графы
@@ -2246,7 +2246,7 @@
<span class="md-nav__icon md-icon"></span>
Глава 9. Граф
Глава 9. Графы
</label>
@@ -2296,7 +2296,7 @@
<span class="md-ellipsis">
9.2 Базовые операции над графами
9.2 Базовые операции графа
@@ -2352,7 +2352,7 @@
<span class="md-ellipsis">
9.4 Резюме
9.4 Краткие итоги
@@ -2491,7 +2491,7 @@
<span class="md-ellipsis">
10.2 Точка вставки двоичного поиска
10.2 Двоичный поиск точки вставки
@@ -2519,7 +2519,7 @@
<span class="md-ellipsis">
10.3 Граничные случаи двоичного поиска
10.3 Двоичный поиск границ
@@ -2547,7 +2547,7 @@
<span class="md-ellipsis">
10.4 Стратегия оптимизации через хеширование
10.4 Стратегии оптимизации хеширования
@@ -2575,7 +2575,7 @@
<span class="md-ellipsis">
10.5 Алгоритмы поиска: новый взгляд
10.5 Переосмысление алгоритмов поиска
@@ -2780,7 +2780,7 @@
<span class="md-ellipsis">
11.3 Пузырьковая сортировка
11.3 Сортировка пузырьком
@@ -2808,7 +2808,7 @@
<span class="md-ellipsis">
11.4 Сортировка вставкой
11.4 Сортировка вставками
@@ -3113,7 +3113,7 @@
<span class="md-ellipsis">
12.1 Алгоритмы разделяй и властвуй
12.1 Стратегия разделяй и властвуй
@@ -3141,7 +3141,7 @@
<span class="md-ellipsis">
12.2 Стратегия поиска разделяй и властвуй
12.2 Поисковая стратегия разделяй и властвуй
@@ -3418,7 +3418,7 @@
<span class="md-ellipsis">
13.4 Задача о $n$ ферзях
13.4 Задача о n ферзях
@@ -3561,7 +3561,7 @@
<span class="md-ellipsis">
14.1 Введение в динамическое программирование
14.1 Первое знакомство с динамическим программированием
@@ -3682,7 +3682,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -3700,7 +3700,7 @@
<span class="md-ellipsis">
14.5 Задача о неограниченном рюкзаке
14.5 Задача о полном рюкзаке
@@ -4611,7 +4611,7 @@
<!-- Page content -->
<h1 id="145">14.5 &nbsp; Задача о полном рюкзаке<a class="headerlink" href="#145" title="Permanent link">&para;</a></h1>
<p>В этом разделе сначала решим еще один распространенный вариант задачи о рюкзаке - полный рюкзак, а затем рассмотрим одну из его типичных специальных форм: задачу о размене монет.</p>
<p>В этом разделе сначала решим еще одну распространенную задачу о рюкзаке - задачу о полном рюкзаке, а затем рассмотрим один из ее типичных частных случаев: задачу о размене монет.</p>
<h2 id="1451">14.5.1 &nbsp; Задача о полном рюкзаке<a class="headerlink" href="#1451" title="Permanent link">&para;</a></h2>
<div class="admonition question">
<p class="admonition-title">Question</p>
@@ -4621,10 +4621,10 @@
<p align="center"> Рисунок 14-22 &nbsp; Пример данных для задачи о полном рюкзаке </p>
<h3 id="1">1. &nbsp; Идея динамического программирования<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<p>Задача о полном рюкзаке очень похожа на задачу о рюкзаке 0-1; <strong>разница состоит только в том, что число выборов каждого предмета не ограничено</strong>.</p>
<p>Задача о полном рюкзаке очень похожа на задачу о рюкзаке 0-1; <strong>разница состоит только в том, что количество выборов каждого предмета не ограничено</strong>.</p>
<ul>
<li>В задаче о рюкзаке 0-1 каждого предмета существует только один экземпляр, поэтому после того как предмет <span class="arithmatex">\(i\)</span> помещен в рюкзак, выбирать можно только из первых <span class="arithmatex">\(i-1\)</span> предметов.</li>
<li>В задаче о полном рюкзаке число экземпляров каждого предмета бесконечно, поэтому после того как предмет <span class="arithmatex">\(i\)</span> помещен в рюкзак, <strong>выбирать все еще можно из первых <span class="arithmatex">\(i\)</span> предметов</strong>.</li>
<li>В задаче о полном рюкзаке количество предметов не ограничено, поэтому после того как предмет <span class="arithmatex">\(i\)</span> помещен в рюкзак, <strong>можно продолжать выбирать из первых <span class="arithmatex">\(i\)</span> предметов</strong>.</li>
</ul>
<p>При этом состояние <span class="arithmatex">\([i, c]\)</span> в задаче о полном рюкзаке может изменяться двумя способами.</p>
<ul>
@@ -5315,9 +5315,9 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
<p align="center"> Рисунок 14-24 &nbsp; Пример данных для задачи о размене монет </p>
<h3 id="1_1">1. &nbsp; Идея динамического программирования<a class="headerlink" href="#1_1" title="Permanent link">&para;</a></h3>
<p><strong>Задачу о размене монет можно рассматривать как частный случай задачи о полном рюкзаке</strong> ; между ними существует следующая связь и следующие различия.</p>
<p><strong>Задачу о размене монет можно рассматривать как частный случай задачи о полном рюкзаке</strong> ; между ними существуют следующие соответствия и различия.</p>
<ul>
<li>Эти две задачи можно взаимно переводить друг в друга: "предмет" соответствует "монете", "вес предмета" соответствует "номиналу монеты", а "вместимость рюкзака" соответствует "целевой сумме".</li>
<li>Эти две задачи можно взаимно преобразовать: "предмет" соответствует "монете", "вес предмета" соответствует "номиналу монеты", а "вместимость рюкзака" соответствует "целевой сумме".</li>
<li>Цель оптимизации противоположна: в задаче о полном рюкзаке нужно максимизировать стоимость предметов, а в задаче о размене монет - минимизировать число монет.</li>
<li>В задаче о полном рюкзаке ищется решение, не превышающее вместимость, а в задаче о размене монет требуется <strong>ровно</strong> набрать целевую сумму.</li>
</ul>
@@ -5337,7 +5337,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
<p>Когда целевая сумма равна <span class="arithmatex">\(0\)</span> , минимальное число монет для ее набора равно <span class="arithmatex">\(0\)</span> , то есть весь первый столбец <span class="arithmatex">\(dp[i, 0]\)</span> заполняется нулями.</p>
<p>Когда монет нет, <strong>невозможно набрать никакую целевую сумму <span class="arithmatex">\(&gt; 0\)</span></strong> ; это и есть недопустимое решение. Чтобы функция <span class="arithmatex">\(\min()\)</span> в уравнении перехода состояния могла распознавать и отбрасывать такие недопустимые решения, удобно использовать значение <span class="arithmatex">\(+ \infty\)</span> ; то есть всю первую строку <span class="arithmatex">\(dp[0, a]\)</span> нужно инициализировать значением <span class="arithmatex">\(+ \infty\)</span> .</p>
<h3 id="2_1">2. &nbsp; Реализация кода<a class="headerlink" href="#2_1" title="Permanent link">&para;</a></h3>
<p>Большинство языков программирования не предоставляет готовую переменную <span class="arithmatex">\(+ \infty\)</span> для целых чисел, поэтому обычно приходится заменять ее на максимальное значение типа <code>int</code> . Но тогда возникает риск переполнения: операция <span class="arithmatex">\(+ 1\)</span> в уравнении перехода может переполнить большое число.</p>
<p>Большинство языков программирования не предоставляет представление для <span class="arithmatex">\(+ \infty\)</span> в целочисленном виде, поэтому обычно приходится заменять его на максимальное значение типа <code>int</code> . Но тогда возникает риск переполнения: операция <span class="arithmatex">\(+ 1\)</span> в уравнении перехода может переполнить большое число.</p>
<p>Поэтому здесь мы используем число <span class="arithmatex">\(amt + 1\)</span> как обозначение недопустимого решения, потому что для набора суммы <span class="arithmatex">\(amt\)</span> максимум нужно не больше чем <span class="arithmatex">\(amt\)</span> монет. Перед возвратом результата проверяем, равно ли <span class="arithmatex">\(dp[n, amt]\)</span> значению <span class="arithmatex">\(amt + 1\)</span> ; если да, то возвращаем <span class="arithmatex">\(-1\)</span> , что означает невозможность набрать целевую сумму. Код приведен ниже:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="4:13"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><input id="__tabbed_4_11" name="__tabbed_4" type="radio" /><input id="__tabbed_4_12" name="__tabbed_4" type="radio" /><input id="__tabbed_4_13" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Python</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Java</label><label for="__tabbed_4_4">C#</label><label for="__tabbed_4_5">Go</label><label for="__tabbed_4_6">Swift</label><label for="__tabbed_4_7">JS</label><label for="__tabbed_4_8">TS</label><label for="__tabbed_4_9">Dart</label><label for="__tabbed_4_10">Rust</label><label for="__tabbed_4_11">C</label><label for="__tabbed_4_12">Kotlin</label><label for="__tabbed_4_13">Ruby</label></div>
<div class="tabbed-content">
@@ -6118,7 +6118,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
<p align="center"> Рисунок 14-26 &nbsp; Пример данных для задачи о размене монет II </p>
<h3 id="1_2">1. &nbsp; Идея динамического программирования<a class="headerlink" href="#1_2" title="Permanent link">&para;</a></h3>
<p>По сравнению с предыдущей задачей теперь целью является число комбинаций. Поэтому подзадача меняется на следующую: <strong>число комбинаций из первых <span class="arithmatex">\(i\)</span> видов монет, которыми можно набрать сумму <span class="arithmatex">\(a\)</span></strong>. При этом таблица <span class="arithmatex">\(dp\)</span> по-прежнему остается двумерной матрицей размера <span class="arithmatex">\((n+1) \times (amt + 1)\)</span> .</p>
<p>По сравнению с предыдущей задачей здесь целью является число комбинаций. Поэтому подзадача меняется на следующую: <strong>число комбинаций из первых <span class="arithmatex">\(i\)</span> видов монет, которыми можно набрать сумму <span class="arithmatex">\(a\)</span></strong>. При этом таблица <span class="arithmatex">\(dp\)</span> по-прежнему остается двумерной матрицей размера <span class="arithmatex">\((n+1) \times (amt + 1)\)</span> .</p>
<p>Число комбинаций для текущего состояния равно сумме числа комбинаций для двух решений: не брать текущую монету и брать текущую монету. Поэтому уравнение перехода состояния принимает вид:</p>
<div class="arithmatex">\[
dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]]