mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-17 01:06:07 +00:00
deploy
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4381,7 +4381,7 @@
|
||||
<h1 id="134-n">13.4 Задача о n ферзях<a class="headerlink" href="#134-n" title="Permanent link">¶</a></h1>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">Question</p>
|
||||
<p>Согласно правилам шахмат ферзь может атаковать фигуры, находящиеся с ним на одной строке, в одном столбце или на одной диагонали. Даны <span class="arithmatex">\(n\)</span> ферзей и шахматная доска размера <span class="arithmatex">\(n \times n\)</span> ; требуется найти такие расстановки, при которых ни одна пара ферзей не может атаковать друг друга.</p>
|
||||
<p>Согласно правилам шахмат ферзь может атаковать фигуры, находящиеся с ним на одной строке, в одном столбце или на одной диагонали. Даны <span class="arithmatex">\(n\)</span> ферзей и шахматная доска размера <span class="arithmatex">\(n \times n\)</span>. Требуется найти такие расстановки, при которых ни одна пара ферзей не может атаковать друг друга.</p>
|
||||
</div>
|
||||
<p>Как показано на рисунке 13-15, при <span class="arithmatex">\(n = 4\)</span> существует два решения. С точки зрения поиска с возвратом доска размера <span class="arithmatex">\(n \times n\)</span> содержит <span class="arithmatex">\(n^2\)</span> клеток, которые образуют все возможные выборы <code>choices</code> . По мере поочередного размещения ферзей состояние доски непрерывно меняется, и текущее содержимое доски образует состояние <code>state</code> .</p>
|
||||
<p><img alt="Решения задачи о 4 ферзях" class="animation-figure" src="../n_queens_problem.assets/solution_4_queens.png" /></p>
|
||||
@@ -4394,7 +4394,7 @@
|
||||
<h3 id="1">1. Построчная стратегия размещения<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>Число ферзей и число строк доски одинаково и равно <span class="arithmatex">\(n\)</span> , поэтому легко получить следующий вывод: <strong>в каждой строке доски разрешено и нужно разместить ровно одного ферзя</strong>.</p>
|
||||
<p>Иначе говоря, можно использовать построчную стратегию: начиная с первой строки, размещать по одному ферзю в каждой строке, пока не будет достигнута последняя.</p>
|
||||
<p>На рисунке 13-17 показан процесс построчного размещения для задачи о 4 ферзях. Из-за ограничений размера изображения на нем раскрыта только одна ветвь поиска для первой строки, а все варианты, не удовлетворяющие ограничениям по столбцам и диагоналям, были отсечены.</p>
|
||||
<p>На рисунке 13-17 показан процесс построчного размещения для задачи о 4 ферзях. Из-за ограничений размера изображения на рисунке 13-17 показана лишь одна ветвь поиска для первой строки, а все варианты, не удовлетворяющие ограничениям по столбцам и диагоналям, были отсечены.</p>
|
||||
<p><img alt="Построчная стратегия размещения" class="animation-figure" src="../n_queens_problem.assets/n_queens_placing.png" /></p>
|
||||
<p align="center"> Рисунок 13-17 Построчная стратегия размещения </p>
|
||||
|
||||
|
||||
@@ -4524,7 +4524,7 @@
|
||||
<p class="admonition-title">Question</p>
|
||||
<p>Дан массив целых чисел, в котором нет повторяющихся элементов. Верните все возможные перестановки.</p>
|
||||
</div>
|
||||
<p>С точки зрения поиска с возвратом <strong>процесс построения перестановок можно представить как результат последовательности выборов</strong>. Пусть входной массив равен <span class="arithmatex">\([1, 2, 3]\)</span> ; если мы сначала выберем <span class="arithmatex">\(1\)</span> , затем <span class="arithmatex">\(3\)</span> , а потом <span class="arithmatex">\(2\)</span> , то получим перестановку <span class="arithmatex">\([1, 3, 2]\)</span> . Откат здесь означает отмену одного из выборов с последующей попыткой других вариантов.</p>
|
||||
<p>С точки зрения поиска с возвратом <strong>процесс построения перестановок можно представить как результат последовательности выборов</strong>. Пусть входной массив равен <span class="arithmatex">\([1, 2, 3]\)</span>. Если мы сначала выберем <span class="arithmatex">\(1\)</span> , затем <span class="arithmatex">\(3\)</span> , а потом <span class="arithmatex">\(2\)</span> , то получим перестановку <span class="arithmatex">\([1, 3, 2]\)</span> . Откат здесь означает отмену одного из выборов с последующей попыткой других вариантов.</p>
|
||||
<p>С точки зрения кода поиска с возвратом множество кандидатов <code>choices</code> состоит из всех элементов входного массива, а состояние <code>state</code> - из элементов, уже выбранных к текущему моменту. Поскольку каждый элемент разрешено выбирать только один раз, <strong>все элементы в <code>state</code> должны быть уникальны</strong>.</p>
|
||||
<p>Как показано на рисунке 13-5, процесс поиска можно развернуть в дерево рекурсии, где каждый узел представляет текущее состояние <code>state</code> . Начиная от корня, после трех раундов выбора мы попадаем в листья, и каждый лист соответствует одной перестановке.</p>
|
||||
<p><img alt="Дерево рекурсии для перестановок" class="animation-figure" src="../permutations_problem.assets/permutations_i.png" /></p>
|
||||
@@ -4540,9 +4540,9 @@
|
||||
<p><img alt="Пример обрезки в задаче о перестановках" class="animation-figure" src="../permutations_problem.assets/permutations_i_pruning.png" /></p>
|
||||
<p align="center"> Рисунок 13-6 Пример обрезки в задаче о перестановках </p>
|
||||
|
||||
<p>Из рисунка видно, что такая обрезка уменьшает размер пространства поиска с <span class="arithmatex">\(O(n^n)\)</span> до <span class="arithmatex">\(O(n!)\)</span> .</p>
|
||||
<p>Как видно на рисунке 13-6, такая обрезка уменьшает размер пространства поиска с <span class="arithmatex">\(O(n^n)\)</span> до <span class="arithmatex">\(O(n!)\)</span> .</p>
|
||||
<h3 id="2">2. Реализация кода<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p>После прояснения всей логики можно просто "заполнить пропуски" в шаблоне поиска с возвратом. Чтобы сократить общий объем кода, мы не будем отдельно реализовывать каждую функцию из каркаса, а раскроем их прямо внутри <code>backtrack()</code> :</p>
|
||||
<p>После прояснения всей логики можно просто «заполнить пропуски» в шаблоне поиска с возвратом. Чтобы сократить общий объем кода, мы не будем отдельно реализовывать каждую функцию из каркаса, а раскроем их прямо внутри <code>backtrack()</code> :</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">
|
||||
@@ -5021,7 +5021,7 @@
|
||||
|
||||
<p>Как же убрать повторяющиеся перестановки? Самый прямолинейный способ - воспользоваться хеш-множеством и удалить дубликаты уже после генерации результата. Но это не слишком изящно, <strong>потому что ветви поиска, порождающие дубликаты, вообще не нужно посещать: их следует распознавать заранее и отсекать</strong>, что дополнительно повышает эффективность алгоритма.</p>
|
||||
<h3 id="1_1">1. Обрезка равных элементов<a class="headerlink" href="#1_1" title="Permanent link">¶</a></h3>
|
||||
<p>Посмотрите на рисунок 13-8: в первом раунде выбрать <span class="arithmatex">\(1\)</span> или выбрать <span class="arithmatex">\(\hat{1}\)</span> - это одно и то же, а значит, все перестановки, полученные из этих двух выборов, будут дублироваться. Поэтому ветвь <span class="arithmatex">\(\hat{1}\)</span> нужно отсечь.</p>
|
||||
<p>Как видно на рисунке 13-8, в первом раунде выбрать <span class="arithmatex">\(1\)</span> или выбрать <span class="arithmatex">\(\hat{1}\)</span> - это одно и то же, а значит, все перестановки, полученные из этих двух выборов, будут дублироваться. Поэтому ветвь <span class="arithmatex">\(\hat{1}\)</span> нужно отсечь.</p>
|
||||
<p>Точно так же, если в первом раунде выбрать <span class="arithmatex">\(2\)</span> , то во втором раунде выборы <span class="arithmatex">\(1\)</span> и <span class="arithmatex">\(\hat{1}\)</span> снова создадут дублирующиеся ветви, поэтому и в этом случае ветвь <span class="arithmatex">\(\hat{1}\)</span> нужно отсечь.</p>
|
||||
<p>Иначе говоря, <strong>наша цель заключается в том, чтобы на каждом раунде выбора каждый из нескольких равных элементов выбирался только один раз</strong>.</p>
|
||||
<p><img alt="Обрезка повторяющихся перестановок" class="animation-figure" src="../permutations_problem.assets/permutations_ii_pruning.png" /></p>
|
||||
@@ -5522,7 +5522,7 @@
|
||||
<p><div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%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%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%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%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B8%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%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%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%83%D0%B6%D0%B5%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%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%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%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%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=13&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%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%20%D0%B1%D1%8D%D0%BA%D1%82%D1%80%D0%B5%D0%BA%D0%B8%D0%BD%D0%B3%D0%B0%3A%20%D0%B2%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%B0%20%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%20%D1%87%D0%B8%D1%81%D0%BB%D1%83%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%2C%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%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%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%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%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%B8%D0%BD%20%D0%B8%20%D1%82%D0%BE%D1%82%20%D0%B6%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B8%20%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F%20%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B1%D0%B8%D1%80%D0%B0%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D1%8B%D0%B5%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%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%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F%20%D1%83%D0%B6%D0%B5%20%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B9%D1%82%D0%B8%20%D0%BA%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B5%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D1%83%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9E%D1%82%D0%BA%D0%B0%D1%82%3A%20%D0%BE%D1%82%D0%BC%D0%B5%D0%BD%D0%B8%D1%82%D1%8C%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B8%20%D0%B2%D0%BE%D1%81%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D1%8B%D0%B4%D1%83%D1%89%D0%B5%D0%B5%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%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%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%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%D0%92%D1%85%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%D0%92%D1%81%D0%B5%20%D0%BF%D0%B5%D1%80%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B8%20res%20%3D%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=13&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<p>Если предположить, что все элементы попарно различны, то из <span class="arithmatex">\(n\)</span> элементов можно получить <span class="arithmatex">\(n!\)</span> перестановок; при записи результата требуется копировать список длины <span class="arithmatex">\(n\)</span> , что занимает <span class="arithmatex">\(O(n)\)</span> времени. <strong>Следовательно, временная сложность равна <span class="arithmatex">\(O(n!n)\)</span></strong> .</p>
|
||||
<p>Если предположить, что все элементы попарно различны, то из <span class="arithmatex">\(n\)</span> элементов можно получить <span class="arithmatex">\(n!\)</span> перестановок. При записи результата требуется копировать список длины <span class="arithmatex">\(n\)</span> , что занимает <span class="arithmatex">\(O(n)\)</span> времени. <strong>Следовательно, временная сложность равна <span class="arithmatex">\(O(n!n)\)</span></strong> .</p>
|
||||
<p>Максимальная глубина рекурсии равна <span class="arithmatex">\(n\)</span> , что требует <span class="arithmatex">\(O(n)\)</span> стековой памяти. Массив <code>selected</code> занимает <span class="arithmatex">\(O(n)\)</span> пространства. Одновременно может существовать до <span class="arithmatex">\(n\)</span> хеш-множеств <code>duplicated</code> , что дает <span class="arithmatex">\(O(n^2)\)</span> памяти. <strong>Следовательно, пространственная сложность равна <span class="arithmatex">\(O(n^2)\)</span></strong> .</p>
|
||||
<h3 id="3">3. Сравнение двух видов обрезки<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>Обратите внимание: хотя и <code>selected</code> , и <code>duplicated</code> используются для обрезки, их цели различаются.</p>
|
||||
|
||||
@@ -4494,7 +4494,7 @@
|
||||
<h2 id="1331">13.3.1 Случай без повторяющихся элементов<a class="headerlink" href="#1331" title="Permanent link">¶</a></h2>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">Question</p>
|
||||
<p>Дан массив положительных целых чисел <code>nums</code> и целое положительное значение <code>target</code> . Найдите все возможные комбинации, сумма элементов которых равна <code>target</code> . Во входном массиве нет повторяющихся элементов, и каждый элемент можно выбирать неограниченное число раз. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций.</p>
|
||||
<p>Дан массив положительных целых чисел <code>nums</code> и целое положительное значение <code>target</code> . Найдите все возможные комбинации, сумма элементов которых равна <code>target</code> . Во входном массиве нет повторяющихся элементов, и каждый элемент можно выбирать неограниченное число раз. Верните эти комбинации в виде списка. В результате не должно быть повторяющихся комбинаций.</p>
|
||||
</div>
|
||||
<p>Например, для входного множества <span class="arithmatex">\(\{3, 4, 5\}\)</span> и целевого значения <span class="arithmatex">\(9\)</span> решениями будут <span class="arithmatex">\(\{3, 3, 3\}\)</span> и <span class="arithmatex">\(\{4, 5\}\)</span> . При этом важно учитывать два обстоятельства.</p>
|
||||
<ul>
|
||||
@@ -4502,7 +4502,7 @@
|
||||
<li>Подмножество не различает порядок элементов, поэтому <span class="arithmatex">\(\{4, 5\}\)</span> и <span class="arithmatex">\(\{5, 4\}\)</span> считаются одним и тем же подмножеством.</li>
|
||||
</ul>
|
||||
<h3 id="1">1. Отталкиваемся от решения задачи о перестановках<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>Как и в задаче о перестановках, можно представлять построение подмножеств как результат последовательности выборов и во время выбора динамически обновлять "сумму элементов"; когда эта сумма становится равной <code>target</code> , соответствующее подмножество записывается в список результатов.</p>
|
||||
<p>Как и в задаче о перестановках, можно представлять построение подмножеств как результат последовательности выборов и во время выбора динамически обновлять «сумму элементов». Когда эта сумма становится равной <code>target</code> , соответствующее подмножество записывается в список результатов.</p>
|
||||
<p>Однако в отличие от задачи о перестановках <strong>в этой задаче элементы множества можно выбирать неограниченное число раз</strong>, поэтому нам не нужен булев список <code>selected</code> для записи того, был ли выбран элемент. Можно слегка изменить код для перестановок и получить первоначальную версию решения:</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">
|
||||
@@ -4999,7 +4999,7 @@
|
||||
<li>Сравнение подмножеств (то есть массивов) само по себе довольно затратно: сначала приходится сортировать массивы, а затем поэлементно сравнивать их.</li>
|
||||
</ul>
|
||||
<h3 id="2">2. Обрезка повторяющихся подмножеств<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p><strong>Поэтому стоит выполнять устранение дубликатов прямо во время поиска, с помощью обрезки</strong>. Посмотрите на рисунок 13-11: повторяющиеся подмножества возникают тогда, когда элементы массива выбираются в разном порядке, например так.</p>
|
||||
<p><strong>Поэтому стоит выполнять устранение дубликатов прямо во время поиска, с помощью обрезки</strong>. Как видно на рисунке 13-11, повторяющиеся подмножества возникают тогда, когда элементы массива выбираются в разном порядке, например так.</p>
|
||||
<ol>
|
||||
<li>Если в первом и втором раундах выбрать соответственно <span class="arithmatex">\(3\)</span> и <span class="arithmatex">\(4\)</span> , то будут сгенерированы все подмножества, содержащие эти два элемента, и их можно обозначить как <span class="arithmatex">\([3, 4, \dots]\)</span> .</li>
|
||||
<li>После этого, если в первом раунде выбрать <span class="arithmatex">\(4\)</span> , <strong>то во втором раунде нужно пропустить <span class="arithmatex">\(3\)</span></strong> , потому что подмножества <span class="arithmatex">\([4, 3, \dots]\)</span> полностью дублируют подмножества, уже построенные на шаге <code>1.</code> .</li>
|
||||
@@ -5013,13 +5013,13 @@
|
||||
<p><img alt="Повторяющиеся подмножества из-за разного порядка выбора" class="animation-figure" src="../subset_sum_problem.assets/subset_sum_i_pruning.png" /></p>
|
||||
<p align="center"> Рисунок 13-11 Повторяющиеся подмножества из-за разного порядка выбора </p>
|
||||
|
||||
<p>В общем виде, если входной массив имеет вид <span class="arithmatex">\([x_1, x_2, \dots, x_n]\)</span> , а последовательность выборов в ходе поиска равна <span class="arithmatex">\([x_{i_1}, x_{i_2}, \dots, x_{i_m}]\)</span> , то она должна удовлетворять условию <span class="arithmatex">\(i_1 \leq i_2 \leq \dots \leq i_m\)</span> ; <strong>все последовательности выборов, не удовлетворяющие этому условию, приводят к дубликатам и должны отсекаться</strong>.</p>
|
||||
<p>В общем виде, если входной массив имеет вид <span class="arithmatex">\([x_1, x_2, \dots, x_n]\)</span> , а последовательность выборов в ходе поиска равна <span class="arithmatex">\([x_{i_1}, x_{i_2}, \dots, x_{i_m}]\)</span> , то она должна удовлетворять условию <span class="arithmatex">\(i_1 \leq i_2 \leq \dots \leq i_m\)</span>. <strong>Все последовательности выборов, не удовлетворяющие этому условию, приводят к дубликатам и должны отсекаться</strong>.</p>
|
||||
<h3 id="3">3. Реализация кода<a class="headerlink" href="#3" title="Permanent link">¶</a></h3>
|
||||
<p>Чтобы реализовать такую обрезку, инициализируем переменную <code>start</code> , которая будет указывать начальную точку обхода. <strong>После выбора элемента <span class="arithmatex">\(x_i\)</span> следующий раунд начинается с индекса <span class="arithmatex">\(i\)</span></strong>. Благодаря этому последовательность выборов всегда удовлетворяет условию <span class="arithmatex">\(i_1 \leq i_2 \leq \dots \leq i_m\)</span> , а значит, каждое подмножество создается только один раз.</p>
|
||||
<p>Помимо этого, мы внесем в код еще два улучшения.</p>
|
||||
<ul>
|
||||
<li>Перед началом поиска отсортируем массив <code>nums</code> . Тогда при обходе всех вариантов <strong>можно сразу прервать цикл, как только сумма подмножества превысит <code>target</code></strong> , потому что все последующие элементы будут еще больше и их сумма тоже превысит <code>target</code> .</li>
|
||||
<li>Откажемся от отдельной переменной суммы <code>total</code> и <strong>будем учитывать сумму через вычитание из <code>target</code></strong> ; когда <code>target</code> станет равным <span class="arithmatex">\(0\)</span> , решение фиксируется.</li>
|
||||
<li>Откажемся от отдельной переменной суммы <code>total</code> и <strong>будем учитывать сумму через вычитание из <code>target</code></strong>. Когда <code>target</code> станет равным <span class="arithmatex">\(0\)</span> , решение фиксируется.</li>
|
||||
</ul>
|
||||
<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">
|
||||
@@ -5526,10 +5526,10 @@
|
||||
<h2 id="1332">13.3.2 Учет повторяющихся элементов<a class="headerlink" href="#1332" title="Permanent link">¶</a></h2>
|
||||
<div class="admonition question">
|
||||
<p class="admonition-title">Question</p>
|
||||
<p>Дан массив положительных целых чисел <code>nums</code> и целое положительное значение <code>target</code> . Найдите все возможные комбинации, сумма элементов которых равна <code>target</code> . <strong>Во входном массиве могут присутствовать повторяющиеся элементы, и каждый элемент разрешено выбирать только один раз</strong>. Верните эти комбинации в виде списка; в результате не должно быть повторяющихся комбинаций.</p>
|
||||
<p>Дан массив положительных целых чисел <code>nums</code> и целое положительное значение <code>target</code> . Найдите все возможные комбинации, сумма элементов которых равна <code>target</code> . <strong>Во входном массиве могут присутствовать повторяющиеся элементы, и каждый элемент разрешено выбирать только один раз</strong>. Верните эти комбинации в виде списка. В результате не должно быть повторяющихся комбинаций.</p>
|
||||
</div>
|
||||
<p>По сравнению с предыдущей задачей <strong>во входном массиве теперь могут присутствовать повторяющиеся элементы</strong>, и это создает новую проблему. Например, если дан массив <span class="arithmatex">\([4, \hat{4}, 5]\)</span> и целевое значение <span class="arithmatex">\(9\)</span> , то существующий код вернет результат <span class="arithmatex">\([4, 5], [\hat{4}, 5]\)</span> , то есть с повторяющимся подмножеством.</p>
|
||||
<p><strong>Причина появления дублей в том, что равные элементы выбираются несколько раз в одном и том же раунде</strong>. На рисунке 13-13 в первом раунде существует три варианта выбора, и два из них равны <span class="arithmatex">\(4\)</span> ; из-за этого появляются две дублирующиеся ветви поиска и, соответственно, повторяющиеся подмножества. Точно так же два элемента <span class="arithmatex">\(4\)</span> во втором раунде тоже порождают дубликаты.</p>
|
||||
<p><strong>Причина появления дублей в том, что равные элементы выбираются несколько раз в одном и том же раунде</strong>. На рисунке 13-13 в первом раунде существует три варианта выбора, и два из них равны <span class="arithmatex">\(4\)</span>. Из-за этого появляются две дублирующиеся ветви поиска и, соответственно, повторяющиеся подмножества. Точно так же два элемента <span class="arithmatex">\(4\)</span> во втором раунде тоже порождают дубликаты.</p>
|
||||
<p><img alt="Повторяющиеся подмножества из-за равных элементов" class="animation-figure" src="../subset_sum_problem.assets/subset_sum_ii_repeat.png" /></p>
|
||||
<p align="center"> Рисунок 13-13 Повторяющиеся подмножества из-за равных элементов </p>
|
||||
|
||||
|
||||
@@ -4360,11 +4360,11 @@
|
||||
<h3 id="1">1. Ключевые выводы<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<ul>
|
||||
<li>Алгоритм поиска с возвратом по своей сути является методом полного перебора: он ищет решения путем обхода пространства решений в глубину. Во время поиска он фиксирует решения, удовлетворяющие условиям, пока не найдет все такие решения или пока обход не завершится.</li>
|
||||
<li>Процесс поиска с возвратом состоит из двух частей: попытки и отката. Он с помощью поиска в глубину пробует разные варианты выбора; когда встречается состояние, не удовлетворяющее ограничениям, алгоритм отменяет предыдущий выбор, возвращается к прошлому состоянию и продолжает пробовать другие варианты. Попытка и откат являются двумя противоположными по направлению действиями.</li>
|
||||
<li>Процесс поиска с возвратом состоит из двух частей: попытки и отката. Он с помощью поиска в глубину пробует разные варианты выбора. Когда встречается состояние, не удовлетворяющее ограничениям, алгоритм отменяет предыдущий выбор, возвращается к прошлому состоянию и продолжает пробовать другие варианты. Попытка и откат являются двумя противоположными по направлению действиями.</li>
|
||||
<li>Задачи поиска с возвратом обычно содержат несколько ограничений, которые можно использовать для обрезки. Обрезка позволяет заранее завершать ненужные ветви поиска и тем самым значительно повышать эффективность.</li>
|
||||
<li>Алгоритм поиска с возвратом в первую очередь применяется для решения поисковых задач и задач с ограничениями. Задачи комбинаторной оптимизации тоже можно решать с его помощью, но для них часто существуют более эффективные или более подходящие методы.</li>
|
||||
<li>Задача о перестановках нацелена на поиск всех возможных перестановок элементов данного множества. Мы используем массив для записи того, был ли выбран каждый элемент, и отсекаем ветви, где один и тот же элемент выбирается повторно, чтобы гарантировать однократный выбор каждого элемента.</li>
|
||||
<li>В задаче о перестановках, если во множестве присутствуют повторяющиеся элементы, в итоговом результате возникнут повторяющиеся перестановки. Поэтому нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз; обычно это реализуется с помощью хеш-множества.</li>
|
||||
<li>В задаче о перестановках, если во множестве присутствуют повторяющиеся элементы, в итоговом результате возникнут повторяющиеся перестановки. Поэтому нужно ограничить выбор равных элементов так, чтобы в каждом раунде каждый из них выбирался только один раз. Обычно это реализуется с помощью хеш-множества.</li>
|
||||
<li>Цель задачи о сумме подмножеств - найти все подмножества данного множества, сумма которых равна целевому значению. В множестве порядок элементов не важен, однако процесс поиска порождает результаты во всех возможных порядках, из-за чего появляются повторяющиеся подмножества. Поэтому перед запуском поиска с возвратом мы сортируем данные и вводим переменную, указывающую начальную точку обхода в каждом раунде, чтобы отсечь ветви, создающие дубликаты.</li>
|
||||
<li>В задаче о сумме подмножеств равные элементы массива также порождают повторяющиеся множества. При наличии предварительной сортировки их можно отсекать, проверяя равенство соседних элементов, и тем самым гарантировать, что в каждом раунде равные элементы будут выбираться только один раз.</li>
|
||||
<li>Задача о <span class="arithmatex">\(n\)</span> ферзях состоит в поиске способов разместить <span class="arithmatex">\(n\)</span> ферзей на доске размера <span class="arithmatex">\(n \times n\)</span> так, чтобы никакие два ферзя не атаковали друг друга. Ограничения этой задачи включают строки, столбцы, главные диагонали и побочные диагонали. Чтобы выполнить ограничение по строкам, используется построчная стратегия размещения, гарантирующая по одному ферзю в каждой строке.</li>
|
||||
@@ -4372,10 +4372,10 @@
|
||||
</ul>
|
||||
<h3 id="2">2. Вопросы и ответы<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p><strong>Q</strong>: Как понять связь между поиском с возвратом и рекурсией?</p>
|
||||
<p>В целом поиск с возвратом - это скорее "алгоритмическая стратегия", а рекурсия больше похожа на "инструмент".</p>
|
||||
<p>В целом поиск с возвратом - это скорее «алгоритмическая стратегия», а рекурсия больше похожа на «инструмент».</p>
|
||||
<ul>
|
||||
<li>Алгоритмы поиска с возвратом обычно реализуются на основе рекурсии. Однако поиск с возвратом - это лишь один из вариантов применения рекурсии, а именно ее использование в поисковых задачах.</li>
|
||||
<li>Структура рекурсии отражает парадигму разбиения на подзадачи и часто применяется для решения задач "разделяй и властвуй", поиска с возвратом, динамического программирования (мемоизированной рекурсии) и других подобных задач.</li>
|
||||
<li>Структура рекурсии отражает парадигму разбиения на подзадачи и часто применяется для решения задач «разделяй и властвуй», поиска с возвратом, динамического программирования (мемоизированной рекурсии) и других подобных задач.</li>
|
||||
</ul>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
Reference in New Issue
Block a user