Я пытаюсь решить проблему под названием «Стены и ворота».
Вот ссылка на проблему. https://leetcode.com/problems/walls-and-gates/description/
Вам дана сетка m X n, комнаты, инициализированные этими тремя возможными значениями.
-1 Стена или препятствие.
0 Ворота.
INF Бесконечность означает пустую комнату.
Мы используем значение 231 - 1 = 2147483647 для представления INF, поскольку вы можете предположить, что расстояние до ворот меньше 2147483647.
Заполните каждую пустую комнату расстоянием до ближайших ворот. Если невозможно добраться до ворот, их следует заполнить INF.
Я пытаюсь использовать DFS для решения вышеуказанной проблемы.
Мой подход заключается в
Пройдите по сетке, используя два индекса i и j.
И везде, где я встречаю значение ячейки 0, я запускаю DFS, так как это Gate в соответствии с определением проблемы.
В алгоритме DFS я сначала проверяю, удовлетворяются границы или нет.
После всех вышеперечисленных случаев я узнаю минимальное значение и количество ячеек и обновляю текущую ячейку с минимальным значением.
Отметьте ячейку как посещенную. Для чего я использую другую логическую сетку.
И затем я прохожу всю ячейку, делая: ряд + 1, ряд - 1, ячейка + 1, ячейка -1.
Вот код:
class Solution {
public void wallsAndGates(int[][] rooms) {
boolean[][] roomsBool = new boolean[rooms.length][rooms[0].length];
for(int i = 0; i < rooms.length; ++i){
for(int j = 0; j < rooms[i].length; ++j){
if (rooms[i][j] == 0){
//if it is a gate we will do a DFS and fill the cells with the appropriate values.
visitRommsDFS(rooms, i, j, 0, roomsBool);
roomsBool = new boolean[rooms.length][rooms[i].length];
}
}
}
}
private void visitRommsDFS(int[][] rooms, int row, int col, int count, boolean[][] roomsBool){
if (row < 0 || row >= rooms.length || col < 0 || col >= rooms[row].length || rooms[row][col] == -1 || roomsBool[row][col] == true || rooms[row][col] == 0 && count > 0) return;
if (rooms[row][col] > 0){
rooms[row][col] = Math.min(rooms[row][col], count);
}
roomsBool[row][col] = true;
visitRommsDFS(rooms, row-1, col, count + 1, roomsBool);
visitRommsDFS(rooms, row+1, col, count + 1, roomsBool);
visitRommsDFS(rooms, row, col+1, count + 1, roomsBool);
visitRommsDFS(rooms, row, col-1, count + 1, roomsBool);
}
}
Проблема в том, что этот код неверен и не дает требуемого правильного результата, чего не хватает в этом решении? Что я должен добавить, чтобы сделать это решение надежным?
Вот пример ввода для проблемы.
[
[0, 2147483647, -1, 2147483647, 2147483647, -1, -1, 0, 0, -1, 2147483647, 2147483647, 0, -1, 2147483647, 2147483647, 2147483647, 2147483647, 0, 2147483647, 0, -1, -1, -1, -1, 2147483647, -1, -1, 2147483647, 2147483647, -1, -1, 0, 0, -1, 0, 0, 0, 2147483647, 0, 2147483647, -1, -1, 0, -1, 0, 0, 0, 2147483647],
[2147483647, 0, -1, 2147483647, 0, -1, -1, -1, -1, 0, 0, 2147483647, 2147483647, -1, -1, 2147483647, -1, -1, 2147483647, 2147483647, -1, 0, -1, 2147483647, 0, 2147483647, -1, 2147483647, 0, 2147483647, 0, 2147483647, -1, 2147483647, 0, 2147483647, -1, 2147483647, 0, 2147483647, 2147483647, 0, -1, 2147483647, -1, -1, -1, 0, 2147483647]
]
и вот мой вывод.
[
[0, 1, -1, 2, 1, -1, -1, 0, 0, -1, 1, 2, 0, -1, 4, 3, 2, 1, 0, 1, 0, -1, -1, -1, -1, 2, -1, -1, 1, 2, -1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 2, -1, -1, 0, -1, 0, 0, 0, 1],
[1, 0, -1, 3, 0, -1, -1, -1, -1, 0, 0, 2, 1, -1, -1, 4, -1, -1, 1, 2, -1, 0, -1, 1, 0, 1, -1, 1, 0, 1, 0, 1, -1, 1, 0, 1, -1, 1, 0, 1, 1, 0, -1, 1, -1, -1, -1, 0, 1]
]
а вот ожидаемый результат.
[
[0, 1, -1, 2, 1, -1, -1, 0, 0, -1, 1, 1, 0, -1, 4, 3, 2, 1, 0, 1, 0, -1, -1, -1, -1, 2, -1, -1, 1, 2, -1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 1, -1, -1, 0, -1, 0, 0, 0, 1],
[1, 0, -1, 1, 0, -1, -1, -1, -1, 0, 0, 1, 1, -1, -1, 4, -1, -1, 1, 2, -1, 0, -1, 1, 0, 1, -1, 1, 0, 1, 0, 1, -1, 1, 0, 1, -1, 1, 0, 1, 1, 0, -1, 1, -1, -1, -1, 0, 1]
]
Еще несколько примеров входных данных и их ожидаемые результаты:
- Пример ввода
[
[2147483647, -1, 0, 2147483647],
[2147483647, 2147483647, 2147483647, -1],
[2147483647, -1, 2147483647, -1],
[0, -1, 2147483647, 2147483647]
]
Ожидаемый результат примера вывода 1.
[
[3, -1, 0, 1],
[2, 2, 1, -1],
[1, -1, 2, -1],
[0, -1, 3, 4]
]
Пример ввода 2.
[
[-1]
]
Ожидаемый результат для примера ввода 2.
[
[-1]
]
Согласен, и теперь эти вещи добавлены в вопрос. Пожалуйста, посмотрите и дайте мне знать о любых других недостатках, спасибо.
Пожалуйста, смотрите минимальный воспроизводимый пример, акцент на минимальный.
Обратите внимание, что связанная проблема находится за стеной подписки.
DFS не подходит для этой проблемы. Вы должны использовать BFS, чтобы найти расстояние до каждой ячейки от ворот.
@MattTimmermans, это было бы проще, да, я согласен, но мы можем использовать DFS, я имею в виду, что в принципе это может быть не очень эффективно, значение счетчика будет уменьшаться, когда мы возвращаемся в DFS, и ячейки должны иметь соответствующее значение !! Пожалуйста, поправьте меня, если я ошибаюсь!!
Да, вы ошибаетесь. «Расстояние» — это длина кратчайшего пути. BFS следует кратчайшим путям. ДФС нет.
Таким образом, значения счетчика в приведенном выше алгоритме не будут давать нам правильное расстояние ячейки от ячейки ворот. Хм... Это логическая ошибка в моем решении?
Ваша ошибка в том, что вы отмечаете комнату, как видно, прежде чем выяснять, нашли ли вы кратчайший путь или нет:
private void visitRommsDFS(int[][] rooms, int row, int col, int count, boolean[][] roomsBool){
if (... roomsBool[row][col] == true ...) {
return;
}
if (rooms[row][col] > 0){
rooms[row][col] = Math.min(rooms[row][col], count);
}
roomsBool[row][col] = true;
// 1
visitRommsDFS(rooms, row-1, col, count + 1, roomsBool);
// 2
visitRommsDFS(rooms, row+1, col, count + 1, roomsBool);
// 3
visitRommsDFS(rooms, row, col+1, count + 1, roomsBool);
// 4
visitRommsDFS(rooms, row, col-1, count + 1, roomsBool);
}
то есть вызов (1) отравляет все достижимые ячейки в roomsBool
значением true
, поэтому вызовы (2), (3) и (4) могут не иметь никакого эффекта.
Быстрое решение состоит в том, чтобы вернуть roomsBool
состояние обратно в конце visitRommsDFS
вызова:
roomsBool[row][col] = true;
visitRommsDFS(rooms, row - 1, col, count + 1, roomsBool);
visitRommsDFS(rooms, row + 1, col, count + 1, roomsBool);
visitRommsDFS(rooms, row, col + 1, count + 1, roomsBool);
visitRommsDFS(rooms, row, col - 1, count + 1, roomsBool);
roomsBool[row][col] = false;
Ну... Кстати, я не уверен, что стоит пытаться оптимизировать эту головоломку без соответствующих тестовых данных...
ваш текущий алгоритм: пусть найдут все комнаты с воротами и пройдут все комнаты, до которых можно добраться от определенных ворот, временная сложность для такого наивного алгоритма явно хуже, чем O(MxNxG)
, лучшее, что вы можете здесь получить, это прекратить обход комнат, если комната ближе к «другой». ворота, а не к текущему, что-то вроде:
public static final int[][] DIRS = new int[][]{new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1}};
public void wallsAndGates(int[][] rooms) {
for (int i = 0; i < rooms.length; ++i) {
for (int j = 0; j < rooms[i].length; ++j) {
if (rooms[i][j] == 0) {
visitRoomsDFS(rooms, i, j);
}
}
}
}
private void visitRoomsDFS(int[][] rooms, int row, int col) {
int distance = rooms[row][col] + 1;
for (int[] dir : DIRS) {
int r = row + dir[0];
int c = col + dir[1];
if (r < 0 || c < 0 || r >= rooms.length || c >= rooms[row].length) {
continue;
}
// optimization: "have seen better" or not a room
if (rooms[r][c] <= distance) {
continue;
}
rooms[r][c] = distance;
visitRoomsDFS(rooms, r, c);
}
}
Однако я считаю, что даже при такой оптимизации временная сложность все равно не лучше O(MxNxG)
, с другой стороны теоретическая временная сложность для этой головоломки не должна быть лучше O(MxN)
- нам все равно нужно обойти все комнаты, а проблема в том, что алгоритм с временной сложностью O(MxN)
существует:
основная идея состоит в том, чтобы проходить комнаты в очень определенном порядке:
1
до каких-то ворот, либо являются стенами, либо мы их уже видели1
до каких-то ворот - такие комнаты либо имеют расстояние 2
до каких-то ворот, либо являются стенами, либо мы их уже видели.public static final int[][] DIRS = new int[][]{new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1}};
public void wallsAndGates(int[][] rooms) {
int rows = rooms.length;
int cols = rooms[0].length;
Deque<int[]> deque = new LinkedList<>();
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (rooms[i][j] == 0) {
deque.add(new int[]{i, j});
}
}
}
int[] room;
while ((room = deque.pollFirst()) != null) {
int distance = rooms[room[0]][room[1]] + 1;
for (int[] dir : DIRS) {
int r = room[0] + dir[0];
int c = room[1] + dir[1];
if (r < 0 || c < 0 || r >= rows || c >= cols) {
continue;
}
// ignoring walls and rooms
// seen previously
if (rooms[r][c] > distance) {
rooms[r][c] = distance;
deque.addLast(new int[]{r, c});
}
}
}
}
Это, то есть отравление всех достижимых ячеек, является особенностью рекурсии. Не так ли? Пожалуйста, объясните что-нибудь еще об этом. Можно ли что-то сделать, чтобы это выполнялось немного быстрее?
Очевидная оптимизация: вы в настоящее время проходите всю матрицу, чтобы сравнить длину пути, с другой стороны, я полагаю, вы можете прекратить обход матрицы, как только все соседи уже имеют более короткий путь.
Откуда мне знать, что путь, который я нашел, является наиболее оптимизированным и самым коротким путем, который существует для соседей, и для этих ячеек не требуется никакой дальнейшей обработки!! Также, пожалуйста, скажите что-нибудь еще об отравлении всех доступных ячеек, кажется, мне нужно немного больше понять это.
Пожалуйста, добавьте больше деталей к уже очень хорошему ответу. Маленькие детали, которые вы добавляете, делают это отличным ответом.
Ответ, данный @Andrey B. Panfilov, правильный, но предоставленная реализация, по идее, не очень отличается от того, что я сделал, за исключением, конечно, ошибки, которую я делал, не возвращаясь правильно, не снова пометив значение ячейки вспомогательного логического массива как ложное в конце, это нужно сделать именно в конце всех обходов со всех сторон.
roomsBool[row][col] = false;
Этот выше шаг имеет решающее значение, чтобы вернуться.
Кроме того, всего одна проверка, что room[row][col] меньше count :-
if (rooms[row][col] < count) {
rooms[row][col] = count;
}
не будет гарантировать, что в этой ячейке хранится наименьшее значение, мы должны позволить рекурсии решить это, какой из них является кратчайшим путем, это будет сделано, когда мы рекурсивно перейдем к ячейке с разных направлений, и чтобы включить рекурсию, выполните то, то есть позволить ему прийти с разных сторон к рассматриваемой ячейке, мы должны сделать
roomsBool[row][col] = false;
после обхода во всех 4-х направлениях.
И предложенная оптимизация может быть легко включена в мое решение. Вот полное рабочее решение.
class Solution {
public void wallsAndGates(int[][] rooms) {
boolean[][] roomsBool = new boolean[rooms.length][rooms[0].length];
for(int i = 0; i < rooms.length; ++i){
for(int j = 0; j < rooms[i].length; ++j){
if (rooms[i][j] == 0){
//if it is a gate we will do a DFS and fill the cells with the appropriate values.
visitRommsDFS(rooms, i, j, 0, roomsBool);
}
}
}
}
private void visitRommsDFS(int[][] rooms, int row, int col, int count, boolean[][] roomsBool){
if (row < 0 || row >= rooms.length || col < 0 || col >= rooms[row].length || rooms[row][col] == -1 || rooms[row][col] == 0 && count > 0) return;
rooms[row][col] = count;
roomsBool[row][col] = true;
if (row > 0 && rooms[row-1][col] > count + 1)
visitRommsDFS(rooms, row-1, col, count + 1, roomsBool);
if (row + 1 < rooms.length && rooms[row+1][col] > count + 1)
visitRommsDFS(rooms, row+1, col, count + 1, roomsBool);
if (col + 1 < rooms[row].length && rooms[row][col + 1] > count + 1)
visitRommsDFS(rooms, row, col+1, count + 1, roomsBool);
if (col > 0 && rooms[row][col - 1] > count + 1)
visitRommsDFS(rooms, row, col-1, count + 1, roomsBool);
roomsBool[row][col] = false;
}
}
По моему скромному мнению, мораль этой истории состоит в том, чтобы возвращаться определенно, возвращаться явно, возвращаться навсегда, но возвращаться выборочно и делать это с умом.
Анализ временной сложности Поскольку пространство поиска представляет собой сетку m X n, сложность DFS, вторая вспомогательная функция равна O (m X n), так как мы покрываем все ячейки сетки, а их m X n. А обрезка, когда мы делаем рекурсивный вызов, гарантирует, что при выполнении DFS больше не будет повторяться обход ячейки.
Если бы я сделал это в BFS, то и временная сложность осталась бы такой же, как O (m X n), здесь выбор BFS и DFS не имел бы значения для временной сложности, так как размер сетки фиксируется заранее и мы проходим каждую ячейку только один раз, если только значение расстояния не меньше.
Если значение расстояния в следующем вызове меньше, чем текущее значение в ячейке, то, очевидно, мы обходим ячейку более 1 раза.
Двигаемся ли мы от центра к все большему и большему концентрическому кругу или слишком углубляемся в одном направлении, в данном случае это не повлияет на сложность.
Почему?
Поскольку количество ячеек в заданной сетке ограничено, и даже если DFS движется в одном направлении, она вынуждена вернуться из этого направления из-за краев и стенок сетки. И продолжить исследование в другом направлении, и в итоге DFS покроет все ячейки. Кроме того, благодаря логике сокращения ненужного обхода мы эффективны при обходе. Как и BFS, стиль обхода. Таким образом, временная сложность обоих обходов асимптотически одинакова с приведенным выше сокращением нежелательных рекурсивных вызовов.
Условие roomsBool[row][col] == true
лишнее, вы можете разбить свой длинный оператор if на несколько if, поместить отладчик на if (roomsBool[row][col] == true)
и обнаружить, что код никогда не достигает этого if.
Конечно, позвольте мне проверить. Но можем ли мы объяснить, почему он не собирается достигать этого заявления.
утверждение «мы видели эту комнату раньше» дополняется утверждением «если предложенное расстояние больше или равно текущему, то комната либо является воротами в стене, либо мы видели ее раньше». Итак, если вы хотите доказать правильность алгоритма, вам нужно доказать правильность rooms[row][col - 1] > count + 1
оптимизации, без этой оптимизации алгоритм имеет экспоненциальную временную сложность.
"Проблема в том, что этот код неверен и не дает требуемого правильного результата" Дайте нам образец ввода. Сообщите нам ожидаемый результат и фактический результат. Это позволяет нам точно воспроизводить то, что вы делаете, и исключает возможность того, что неверным является не код, а ваши ожидания.