предисловие
в моей последней статье«Сложен ли полный алгоритм перестановки артикула внешнего интерфейса электронной коммерции? Изучите эту процедуру и полностью овладейте перестановкой и комбинацией. 》Рекурсивное решение перестановки и комбинации подробно объясняется в книге Я считаю, что друзья, которые видели его, в определенной степени овладели этой процедурой (студенты, которые не видели этого, вернутся и узнают~).
Это сложная задача на LeetCode, звучит страшно, но студенты, которые читали мою последнюю статью, должны помнить, что я упоминал, что использовал универсальный шаблон перестановки и комбинации для решения проблемы sku электронной коммерции.Можно ли этот универсальный шаблон быть используется для решения классической компьютерной задачи "N Queens"? Ответ положительный.
вопрос
Давайте сначала посмотрим на проблему.На самом деле, проблема не сложна для понимания:
Задача о n ферзях изучает, как разместить n ферзей на шахматной доске размером n на n так, чтобы ферзи не могли атаковать друг друга.
На рисунке выше показано решение задачи о восьми ферзях.
По заданному целому числу n выведите решения всех различных задач с n ферзями.
Каждое решение содержит явную схему размещения пешек для задачи с n ферзями, где «Q» и «.» обозначают ферзей и вакансии соответственно.
Пример:
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
намекать:
Ферзь, пешка в шахматах, означает жену короля. Королева делает только одно: «ест детей». Когда она встречает пешку, которую можно съесть, она быстро бросается за ней. Конечно, она может делать от одного до семи шагов по горизонтали, вертикали и диагонали, а также может наступать или отступать. (Цитата из энциклопедии Baidu - Queen)
идеи
На первый взгляд, в проблеме выбора всех планов немного сложно найти разгадку, но на самом деле, если присмотреться, название ограничило, что ферзи не могут атаковать друг друга, а язык, переведенный на кодовое мышление, на самом деле означает, чтоВ каждом ряду может быть только один ферзь, и только один ферзь на каждой диагонали.,
То есть:
- В одном столбце неправильно.
[
'Q', 0
'Q', 0
]
- В левом верхнем углу -> правая нижняя диагональ, неправильно.
[
'Q', 0
0, 'Q'
]
- В нижней левой -> верхней правой диагонали, неправильно.
[
0, 'Q'
'Q', 0
]
Затем, основываясь на этой идее, мы можем превратить эту проблему в задачу «расставить ферзей ряд за рядом» и подумать о том, как проектировать рекурсивные функции?
дляn皇后
для решения мы можем разработать функцию, которая принимает следующие параметры:
-
rowIndex
Параметр, представляющий ряд, в котором в данный момент пытается разместить ферзя. -
prev
Параметр, представляющий положение ферзя, помещенного в предыдущий ряд, например[1, 3]
Это означает, что ферзь строки 0 (индекс массива) помещается в позицию 1, а ферзь строки 1 помещается в позицию 3.
когдаrowIndex === n
То есть эта рекурсивная успешно разместила N Queen, полностью беспрепятственно дойдя до конца, каждый раз, когда размещение проходило наши условия ограничения, то на этот разprev
помещается в результате в глобальнуюres
в массиве результатов.
Дерево
Здесь я пытаюсь рисовать инструментами4皇后
Одно из решений рекурсивных дендрограмм, первая строка, которую я выбрал непосредственно для размещения ферзя放在2
в качестве отправной точки, опуская放在1
,放在3
,放在4
Дендрограмма в качестве отправной точки, иначе рекурсивное дерево будет слишком большим, чтобы соответствовать картинке.
Обратите внимание здесь放在x
, для простоты понимания этоx
Не индекс массива, а счет, основанный на 1.
После этой рекурсии получается результат:[1, 3, 0, 2]
.
Вы можете продолжать рисовать таким образом на бумаге, чтобы попробовать решения из других отправных точек, чтобы увидеть конкретный поток этого алгоритма.
выполнить
Идеалы всегда прекрасны.Хотя наше мышление пока ясно, есть еще несколько головных болей в конкретном кодировании.
После того, как текущая линия сбросила ферзя, следующая линия должна оценить три условия:
- Раньше на эту колонку нельзя было ставить ферзей.
- На диагональной линии 1, то есть «внизу слева -> вверху справа», ферзя нельзя поставить раньше.
- На диагональной линии 2, то есть «вверху справа -> внизу слева», ферзя нельзя поставить раньше.
Трудность заключается в том, чтобы определить, был ли поставлен ферзь по диагонали.На самом деле найти закономерность несложно.Смотрите на картинку:
对角线1
:
Непосредственно через абсциссу и ординату этой точкиrowIndex + columnIndex
Прибавьте, если равны, на той же диагонали 1:
对角线2
:
Непосредственно через абсциссу и ординату этой точкиrowIndex - columnIndex
Вычитание, равное на той же диагонали 2:
так:
- использовать
columns
запись массива размещенаСписокПодстрочный индекс, после его размещения, отметьте его как истинный напрямую. - использовать
dia1
запись массива размещенаДиагональ 1Подстрочный индекс, поставьте подстрочный индекс сразу после его размещенияrowIndex + columnIndex
Просто отметьте это как истинное. - использовать
dia2
запись массива размещенаДиагональ 2Подстрочный индекс, поставьте подстрочный индекс сразу после его размещенияrowIndex - columnIndex
Просто отметьте это как истинное. - Аргументы для рекурсивных функций
prev
Представляет количество столбцов, в которых помещаются ферзи в каждой строке, например.prev[0] = 3
Представляет ферзей строки 0 в столбце 3 и т. д. - Каждый раз перед входом в рекурсивную функцию сначала ставить соответствующее значение текущего элементаСтолбец, Диагональ 1, Диагональ 2Нижний индекс помечен как истина и входит в рекурсивную функцию с помеченным состоянием. И после выхода из этой рекурсии нужно сбросить эти состояния в false, а затем войти в следующий цикл.
С этими вспомогательными точками знаний мы можем начать писать рекурсивные функции.В каждой строке мы продолжаем пробовать координатную точку.Пока это не противоречит предыдущим результатам, мы можем поместить ее в массив как Начальное значение для следующая рекурсия.
Таким образом, если рекурсивная функция прибывает успешноrowIndex === n
, что означает выполнение всех предыдущих условий, т.n皇后
получается раствор. Пучокprev
Этот одномерный массив можно восстановить до полного «двумерного массива», требуемого заголовком, с помощью вспомогательной функции.
/**
* @param {number} n
* @return {string[][]}
*/
let solveNQueens = function (n) {
let res = []
// 已摆放皇后的的列下标
let columns = []
// 已摆放皇后的对角线1下标 左下 -> 右上
// 计算某个坐标是否在这个对角线的方式是「行下标 + 列下标」是否相等
let dia1 = []
// 已摆放皇后的对角线2下标 左上 -> 右下
// 计算某个坐标是否在这个对角线的方式是「行下标 - 列下标」是否相等
let dia2 = []
// 在选择当前的格子后 记录状态
let record = (rowIndex, columnIndex, bool) => {
columns[columnIndex] = bool
dia1[rowIndex + columnIndex] = bool
dia2[rowIndex - columnIndex] = bool
}
// 尝试在一个n皇后问题中 摆放第index行内的皇后位置
let putQueen = (rowIndex, prev) => {
if (rowIndex === n) {
res.push(generateBoard(prev))
return
}
// 尝试摆第index行的皇后 尝试[0, n-1]列
for (let columnIndex = 0; columnIndex < n; columnIndex++) {
// 在列上不冲突
let columnNotConflict = !columns[columnIndex]
// 在对角线1上不冲突
let dia1NotConflict = !dia1[rowIndex + columnIndex]
// 在对角线2上不冲突
let dia2NotConflict = !dia2[rowIndex - columnIndex]
if (columnNotConflict && dia1NotConflict && dia2NotConflict) {
// 都不冲突的话,先记录当前已选位置,进入下一轮递归
record(rowIndex, columnIndex, true)
putQueen(rowIndex + 1, prev.concat(columnIndex))
// 递归出栈后,在状态中清除这个位置的记录,下一轮循环应该是一个全新的开始。
record(rowIndex, columnIndex, false)
}
}
}
putQueen(0, [])
return res
}
// 生成二维数组的辅助函数
function generateBoard(row) {
let n = row.length
let res = []
for (let y = 0; y < n; y++) {
let cur = ""
for (let x = 0; x < n; x++) {
if (x === row[y]) {
cur += "Q"
} else {
cur += "."
}
}
res.push(cur)
}
return res
}
Суммировать
Пока что первая тяжелая проблема молодого фронтенда решена.Ощущение, что у губернатора вскрылись две жилки?
Суть проблемы рекурсивного возврата заключается в том, что после рекурсивного входа на следующий слой, если он обнаружит, что условие не выполняется, он вернется на предыдущий слой рекурсии посредством возврата и продолжит поиск подходящего решения.
Освоив эту идею, я считаю, что многие рекурсивные проблемы, с которыми вы сталкиваетесь в реальном кодировании, могут быть легко решены путем уменьшения размерности.
Также желаю друзьям, которые готовятся к смене работы, успешно пройти собеседование и письменный тест и получить идеальное предложение.
❤️ Всем спасибо
1. Если эта статья была вам полезна, пожалуйста, поддержите ее лайком, ваш "лайк" - движущая сила моего творчества.
2. Подпишитесь на официальный аккаунт «Front-end from advanced to accept», чтобы добавить меня в друзья, я втяну вас в «Front-end группу расширенного обмена», все смогут общаться и добиваться прогресса вместе.