“我好想逃却逃不掉”题解 | 豆包MarsCode AI刷题

268 阅读4分钟

题解:我好想逃却逃不掉

当初选择这道题主要是因为题目名称比较另类,就点进去看了看,关键点在于要仔细理解下传送带的机制,只要想到反向求可以到达的位置,就很容易联想到DFS了

题目理解

给定一个 N × M 的竞技场迷宫,目标是找到其中所有”危险位置”的数量。所谓”危险位置”,即如果站在该位置上,无论采取什么策略,都无法到达出口。迷宫的元素包括普通地板、出口、以及各种方向的传送器。

普通地板 (’.’) :可以自由移动到相邻的上下左右格子。

出口 (‘O’) :目标位置,任何能够到达此位置的地方都可以算作安全位置。

传送器 (‘U’, ‘D’, ‘L’, ‘R’) :踩上后会被强制传送到相应方向的相邻格子。传送器的特殊性质使得迷宫的探索变得复杂。

解题思路

为了找出所有的”危险位置”,我们首先需要确定哪些位置是安全的。安全的位置是指能够到达出口的位置。通过深度优先搜索(DFS)可以找到所有能够到达出口的区域,然后我们再反推哪些位置是无法到达出口的,也即是”危险位置”。

思路步骤:

  1. 找到出口位置: 先遍历迷宫,找到出口的位置。

  2. 从出口开始深度优先搜索: 从出口出发,利用 DFS 遍历所有可以到达的位置,将它们标记为安全的。

  3. 计算危险位置数量: 迷宫中所有没有被标记为安全的位置,都是”危险位置”。

DFS 详细实现

我们使用 DFS 从出口开始向四个方向探索,逐步标记可达的区域。传送器的处理需要特别注意:

• 如果踩上’R’,会被强制传送到右边的格子;

• 如果踩上’U’,会被强制传送到上方的格子;

• 如果踩上’D’,会被强制传送到下方的格子;

• 如果踩上’L’,会被强制传送到左边的格子。

DFS 的过程:

  1. 对于每一个位置,首先判断是否越界,如果越界则直接返回。

  2. 判断当前位置是否已经访问过,如果已经访问则跳过。

  3. 判断当前位置的内容,若是’出口’,则返回,不再继续探索。

  4. 对于普通地板和传送器,根据其方向执行相应的 DFS。

代码实现

package main

import (
	"fmt"
)

// 不考虑传送带 修改可达位置为X
func dfs(i, j int, data [][]rune) int {
	// 修改当前位置
	data[i][j] = 'X'

	sum := 1
	
	// 试图往上走
	if i - 1 >= 0 && (data[i - 1][j] == 'D' || data[i - 1][j] == '.'){
		sum += dfs(i - 1, j, data)
	}
	// 试图往下走
	if i + 1 < len(data) && (data[i + 1][j] == 'U' || data[i + 1][j] == '.'){
		sum += dfs(i + 1, j ,data)
	}
	// 试图往左走
	if j - 1 >= 0 && (data[i][j - 1] == 'R' || data[i][j - 1] == '.'){
		sum += dfs(i, j - 1,data)
	}
	// 试图往右走
	if j + 1 < len(data[0]) && (data[i][j + 1] == 'L' || data[i][j + 1] == '.'){
		sum += dfs(i, j + 1,data)
	}

	return sum
}

func solution(N, M int, data [][]rune) int {
	exitI := 0
	exitJ := 0

	n := len(data)
	m := len(data[0])

	// 找出口
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if data[i][j] == 'O' {
				// 找到出口
				exitI = i
				exitJ = j
			}
		}
	}

	x := dfs(exitI, exitJ, data)

	return n * m - x
}

func main() {
	pattern1 := [][]rune{
		{'.', '.', '.', '.', '.'},
		{'.', 'R', 'R', 'D', '.'},
		{'.', 'U', '.', 'D', 'R'},
		{'.', 'U', 'L', 'L', '.'},
		{'.', '.', '.', '.', 'O'},
	}
	pattern2 := [][]rune{
		{'.', '.', 'R', '.', '.'},
		{'.', '.', 'R', '.', '.'},
		{'.', '.', 'R', '.', '.'},
		{'.', '.', 'D', 'U', 'U'},
		{'.', '.', '.', '.', 'O'},
	}
	pattern3 := [][]rune{
		{'.', 'D', 'L', 'L', '.'},
		{'.', 'D', '.', 'U', '.'},
		{'.', 'D', 'O', 'U', '.'},
		{'.', 'D', '.', 'U', '.'},
		{'.', 'R', 'R', 'U', '.'},
	}
	fmt.Println(solution(5, 5, pattern1) == 10)
	fmt.Println(solution(5, 5, pattern2) == 11)
	fmt.Println(solution(5, 5, pattern3) == 22)
}

关键点说明:

  1. DFS遍历: 我们从出口出发,遍历所有可以到达的区域,每遍历一个位置就将其标记为已经访问(修改为’X’)。

  2. 如何处理传送器: 传送器并不会产生死循环,因为我们从出口出发,且每个传送器只能将你传送到相邻的一个格子。在 DFS 过程中,传送器本质上就是一种强制移动。

  3. 计算”危险位置”: DFS 完成后,所有没有被访问到的位置就可以认为是”危险位置”。

时间复杂度

时间复杂度: O(N * M),其中 N 和 M 分别是迷宫的行数和列数。因为我们最多会访问每个位置一次。

空间复杂度: O(N * M),用于存储迷宫的状态(每个位置的状态),即使在 DFS 中不做额外的存储,递归深度也最大为 N * M,因此空间复杂度是 O(N * M)。