mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-13 07:46:06 +00:00
fix Ru translation (#1894)
* docs(ru): replace prose quotes with guillemets * docs(ru): replace prose semicolons with periods * docs(ru): align animation title forms * docs(ru): align figure and table references
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
|
||||

|
||||
|
||||
Цель этой задачи - вычислить количество способов. **Поэтому можно попробовать использовать для ее решения метод поиска с возвратом**. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на $1$ или на $2$ ступени; всякий раз, когда достигаем вершины, увеличиваем число способов на $1$ , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так:
|
||||
Цель этой задачи - вычислить количество способов. **Поэтому можно попробовать использовать для ее решения метод поиска с возвратом**. Если представить подъем по лестнице как последовательность решений, то мы начинаем от земли и на каждом раунде выбираем прыжок на $1$ или на $2$ ступени. Всякий раз, когда достигаем вершины, увеличиваем число способов на $1$ , а если перескакиваем вершину, обрезаем эту ветвь. Код выглядит так:
|
||||
|
||||
```src
|
||||
[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack}
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
## Метод 1: полный перебор
|
||||
|
||||
Алгоритм поиска с возвратом обычно не раскладывает задачу явно на подзадачи; вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов.
|
||||
Алгоритм поиска с возвратом обычно не раскладывает задачу явно на подзадачи. Вместо этого он рассматривает решение как последовательность решений, используя попытки и обрезку для поиска всех возможных ответов.
|
||||
|
||||
Попробуем посмотреть на задачу именно как на разложение подзадач. Пусть число способов добраться до ступени $i$ равно $dp[i]$ ; тогда $dp[i]$ - это исходная задача, а ее подзадачи включают:
|
||||
Попробуем посмотреть на задачу именно как на разложение подзадач. Пусть число способов добраться до ступени $i$ равно $dp[i]$. Тогда $dp[i]$ - это исходная задача, а ее подзадачи включают:
|
||||
|
||||
$$
|
||||
dp[i-1], dp[i-2], \dots, dp[2], dp[1]
|
||||
@@ -52,7 +52,7 @@ $$
|
||||
|
||||

|
||||
|
||||
Если посмотреть на рисунок выше, то видно, что **экспоненциальная временная сложность порождается "перекрывающимися подзадачами"**. Например, $dp[9]$ раскладывается в $dp[8]$ и $dp[7]$ , а $dp[8]$ - в $dp[7]$ и $dp[6]$ ; обе ветви содержат подзадачу $dp[7]$ .
|
||||
Как видно на рисунке выше, **экспоненциальная временная сложность порождается «перекрывающимися подзадачами»**. Например, $dp[9]$ раскладывается в $dp[8]$ и $dp[7]$ , а $dp[8]$ - в $dp[7]$ и $dp[6]$. Обе ветви содержат подзадачу $dp[7]$ .
|
||||
|
||||
Продолжая это рассуждение, мы видим, что подзадачи порождают все более мелкие перекрывающиеся подзадачи без конца. Подавляющая часть вычислительных ресурсов уходит именно на них.
|
||||
|
||||
@@ -75,11 +75,11 @@ $$
|
||||
|
||||
## Метод 3: динамическое программирование
|
||||
|
||||
**Поиск с мемоизацией - это метод "сверху вниз"** : мы начинаем с исходной задачи (корня), рекурсивно раскладываем более крупные подзадачи на меньшие, пока не достигнем наименьших подзадач с уже известным ответом (листьев). Затем в процессе возврата постепенно собираем решения подзадач и тем самым получаем решение исходной задачи.
|
||||
**Поиск с мемоизацией - это метод «сверху вниз»** : мы начинаем с исходной задачи (корня), рекурсивно раскладываем более крупные подзадачи на меньшие, пока не достигнем наименьших подзадач с уже известным ответом (листьев). Затем в процессе возврата постепенно собираем решения подзадач и тем самым получаем решение исходной задачи.
|
||||
|
||||
Напротив, **динамическое программирование - это метод "снизу вверх"** : начиная с решений наименьших подзадач, мы итеративно строим решения для более крупных подзадач, пока не получим ответ на исходную задачу.
|
||||
Напротив, **динамическое программирование - это метод «снизу вверх»** : начиная с решений наименьших подзадач, мы итеративно строим решения для более крупных подзадач, пока не получим ответ на исходную задачу.
|
||||
|
||||
Поскольку в динамическом программировании нет этапа возврата, для его реализации достаточно обычных циклов, без рекурсии. В приведенном ниже коде мы инициализируем массив `dp` для хранения решений подзадач; он выполняет ту же роль, что и массив `mem` в мемоизированном поиске:
|
||||
Поскольку в динамическом программировании нет этапа возврата, для его реализации достаточно обычных циклов, без рекурсии. В приведенном ниже коде мы инициализируем массив `dp` для хранения решений подзадач. Он выполняет ту же роль, что и массив `mem` в мемоизированном поиске:
|
||||
|
||||
```src
|
||||
[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp}
|
||||
@@ -89,7 +89,7 @@ $$
|
||||
|
||||

|
||||
|
||||
Как и в поиске с возвратом, в динамическом программировании используется понятие "состояние" для обозначения некоторого этапа решения задачи; каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени $i$ .
|
||||
Как и в поиске с возвратом, в динамическом программировании используется понятие «состояние» для обозначения некоторого этапа решения задачи. Каждое состояние соответствует одной подзадаче и ее локально оптимальному решению. Например, в задаче о лестнице состояние определяется текущим номером ступени $i$ .
|
||||
|
||||
На основе сказанного можно подвести несколько часто используемых терминов динамического программирования.
|
||||
|
||||
@@ -99,7 +99,7 @@ $$
|
||||
|
||||
## Оптимизация пространства
|
||||
|
||||
Внимательный читатель мог заметить, что **поскольку $dp[i]$ зависит только от $dp[i-1]$ и $dp[i-2]$ , нам не нужен весь массив `dp` для хранения ответов всех подзадач** ; достаточно двух переменных, которые будут "перекатываться" вперед. Код имеет вид:
|
||||
Внимательный читатель мог заметить, что **поскольку $dp[i]$ зависит только от $dp[i-1]$ и $dp[i-2]$ , нам не нужен весь массив `dp` для хранения ответов всех подзадач**. Достаточно двух переменных, которые будут «перекатываться» вперед. Код имеет вид:
|
||||
|
||||
```src
|
||||
[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp}
|
||||
@@ -107,4 +107,4 @@ $$
|
||||
|
||||
Из кода видно, что после отказа от массива `dp` пространственная сложность уменьшается с $O(n)$ до $O(1)$ .
|
||||
|
||||
Во многих задачах динамического программирования текущее состояние зависит лишь от ограниченного числа предыдущих состояний. Тогда можно сохранять только действительно нужные состояния и за счет "уменьшения размерности" экономить память. **Этот прием оптимизации памяти называют "скользящими переменными" или "скользящим массивом"**.
|
||||
Во многих задачах динамического программирования текущее состояние зависит лишь от ограниченного числа предыдущих состояний. Тогда можно сохранять только действительно нужные состояния и за счет «уменьшения размерности» экономить память. **Этот прием оптимизации памяти называют «скользящими переменными» или «скользящим массивом»**.
|
||||
|
||||
Reference in New Issue
Block a user