diff --git a/top-interview-leetcode150/graph/130被围绕的区域.js b/top-interview-leetcode150/graph/130被围绕的区域.js new file mode 100644 index 0000000..c735cf8 --- /dev/null +++ b/top-interview-leetcode150/graph/130被围绕的区域.js @@ -0,0 +1,123 @@ +/** + * @param {character[][]} board + * @return {void} Do not return anything, modify board in-place instead. + */ +const solve = function (board) { + +}; + +/* +题目要求我们把被X围绕的O区域修改成X,所以只需要找出所有没有被X围绕的O区域,把这一部分区域修改成A,最后遍历整个矩阵,把所有为O的区域 +修改成X,所有为A的区域修改成O,所有在边上的O,及其相邻的O一定是没有被包围的,所以我们只需要遍历四条边上的O把它们修改成A即可,这里使用 +dfs +*/ +function f1(board) { + // 如果矩阵为空(虽然测试用例保证 1<=m,n) + if (board.length === 0 || board[0].length === 0) return; + + // 获取矩阵的行m和列n,用于后续遍历 + const m = board.length; + const n = board[0].length; + + // 遍历矩阵的第一列和最后一列 + for (let i = 0; i < m; i++) { + dfs(i, 0); + dfs(i, n - 1); + } + // 遍历矩阵的第一行和最后一行 + for (let j = 0; j < n; j++) { + dfs(1, j); + dfs(m - 1, j); + } + + // 遍历所有矩阵,将O修改成X,将A修改成O + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + if (board[i][j] === 'O') board[i][j] = 'X'; + if (board[i][j] === 'A') board[i][j] = 'O'; + } + } + + /* + dfs遍历,将O修改为A + 注意:board直接找外部作用域,无需传递 + */ + let dfs = (x, y) => { + // 如果越界,或者board[x][y] !== 'O'直接return + if (x < 0 || y < 0 || x === m || y === n || board[x][y] !== 'O') return; + board[x][y] = 'A'; // 将O修改成A + // 递归处理四周的O + dfs(board, x - 1, y); + dfs(board, x, y - 1); + dfs(board, x + 1, y); + dfs(board, x, y + 1); + }; +} + +/* +思路和上面一致,使用广度优先遍历BFS +*/ +function f2(board) { + // 定义查找偏移量 + const dx = [1, -1, 0, 0]; + const dy = [0, 0, 1, -1]; + + // 如果矩阵为空(虽然测试用例保证 1<=m,n) + if (board.length === 0 || board[0].length === 0) return; + + // 获取矩阵的行m和列n,用于后续遍历 + const m = board.length; + const n = board[0].length; + + const queue = []; // 广度遍历的队列,是一个二维数组,储存下标[row, col] 表示几行几列 + + // 遍历第一行和最后一行,如果发现O,就将他修改成A并且将它四周的位置压入队列,继续处理 + for (let i = 0; i < n; i++) { + if (board[0][i] === 'O') { + board[0][i] = 'A'; + queue.push([0, i]); + } + + if (board[m - 1][i] === 'O') { + board[m - 1][i] = 'A'; + queue.push([m - 1, i]); + } + } + + // 处理第一列和最后一列,这里需要注意四个角落的元素可以不处理,为了统一,这一我就不做处理,对结果不影响 + + for (let i = 0; i < m; i++) { + if (board[i][0] === 'O') { + board[i][0] = 'A'; + queue.push([i, 0]); + } + + if (board[i][n - 1] === 'O') { + board[i][n - 1] = 'A'; + queue.push([i, n - 1]); + } + } + + // 开始广度遍历 + while (queue.length > 0) { + const [x, y] = queue.pop(); // 获取位置坐标 + + // 通过偏移量寻找四周为O的位置,将其修改为A,然后将其压入栈中,继续先四周寻找 + for (let i = 0; i < 4; i++) { + const mx = x + dx[i]; + const my = y + dy[i]; + // 如果越界 或 不等于O直接跳过 + if (mx < 0 || mx >= m || y < 0 || my >= n || board[mx][my] !== 'O') continue; + board[mx][my] = 'A'; + queue.push([mx, my]); + } + } + + // 遍历所有矩阵,将O修改成X,将A修改成O + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + if (board[i][j] === 'O') board[i][j] = 'X'; + if (board[i][j] === 'A') board[i][j] = 'O'; + } + } +} diff --git a/top-interview-leetcode150/graph/133克隆图.js b/top-interview-leetcode150/graph/133克隆图.js new file mode 100644 index 0000000..25254d2 --- /dev/null +++ b/top-interview-leetcode150/graph/133克隆图.js @@ -0,0 +1,85 @@ +/** + * // Definition for a _Node. + * function _Node(val, neighbors) { + * this.val = val === undefined ? 0 : val; + * this.neighbors = neighbors === undefined ? [] : neighbors; + * }; + * https://leetcode.cn/problems/clone-graph/?envType=study-plan-v2&envId=top-interview-150 + */ + +/** + * @param {_Node} node + * @return {_Node} + */ +const cloneGraph = function (node) { + +}; + +// eslint-disable-next-line no-underscore-dangle, no-unused-vars +function _Node(val, neighbors) { + this.val = val === undefined ? 0 : val; + this.neighbors = neighbors === undefined ? [] : neighbors; +} + +/* +只给了我们一个连通图的一个节点,叫我们克隆整个图,根据两图图的定义,从这个节点出发我们能到达途中的任意一个节点(顶点), +所以我们只需要遍历所有的neighbor,然后顺着邻居继续遍历邻居即可遍历整个图,但是这里有一个问题,就是如果通过邻居到达了 +自己,或者邻居的邻居,是自己的邻居,就会发生循环,这里就遇到使用一种数据结果来缓存已经遍历过的邻居,当再次遇到它时跳过就行。 + +抽象过程:你需要统计小王家人,你只认识小王,于是题通过小王认识了它的爸爸,妈妈,把他们加入到统计表,然后小王的爸爸向你介绍了 +它的妹妹,同时小王向你介绍了它的姑姑,但是你发现这两个人是同一个人,所以你只统计了一次,最后你就获得了所有和小王有关的人。 +*/ +function f1(node) { + const visited = new Map(); // 储存原节点,和克隆节点的映射 + /** + * + * @param {*} node 需要克隆的节点 + * @returns 返回克隆的节点 + */ + function clone(node) { + if (!node) return node; + + // 如果这个节点之前克隆过,直接返回其克隆节点 + if (visited.has(node)) return visited.get(node); + + // 创建这个节点的克隆节点 + const cloneNode = new _Node(node.val, []); + + // node与cloneNode建立映射 + visited.set(node, cloneNode); + + // 为克隆节点克隆邻居节点 + for (const neighbor of node.neighbors) { + cloneNode.neighbors.push(clone(neighbor)); + } + return cloneNode; + } + + return clone(node); +} + +/* +思路和上面一致但是使用广度优先遍历 +*/ +function f2(node) { + if (!node) return node; + + const visited = new Map(); + const queue = [node]; // 用于广度遍历 + const cNode = new _Node(node.val, []); + visited.set(node, cNode); // 克隆第一个节点,将其存入visited + + while (queue.length > 0) { + const n = queue.shift(); + // 遍历这个节点的所有邻居,如果没有访问过将其clone,并加入visited + for (const neighbor of n.neighbors) { + if (!visited.has(neighbor)) { + visited.set(neighbor, new _Node(neighbor.val, [])); + queue.push(neighbor); + } + visited.get(n).neighbors.push(visited.get(neighbor)); + } + } + + return cNode; +} diff --git a/top-interview-leetcode150/graph/200岛屿的数量.js b/top-interview-leetcode150/graph/200岛屿的数量.js new file mode 100644 index 0000000..ba77ca5 --- /dev/null +++ b/top-interview-leetcode150/graph/200岛屿的数量.js @@ -0,0 +1,116 @@ +/** + * @param {character[][]} grid + * @return {number} + * https://leetcode.cn/problems/number-of-islands/?envType=study-plan-v2&envId=top-interview-150 + */ +const numIslands = function (grid) { + +}; + +/* +使用深度优先遍历,在character[][]这个二维数组中,我们认为相邻的‘1’之间都有一条边,首先遍历这个二维数组,从遇到的第一个为1的位置,递归 +的开始向四周查找其他位置是否也为1,如果是的话就继续寻找,并且把找过的位置设置为0,计数加一即可,先当与图中有几个连通分量 +*/ + +function f1(grid) { + /* + 递归函数向grid[r][c]四周继续递归寻找为1的位置,并把它设置成0,当整个为1位置都为0,表示整个岛屿的小时 + */ + const dfs = (grid, r, c) => { + // 如果r,c超出边界,或者grid[r][c] === 0 表示岛屿的边界结束 + if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] === '0') return; + // 将当前位置设置为0,表示已经访问过 + grid[r][c] = '0'; + // 递归的查找四周 + dfs(grid, r - 1, c); // 上 + dfs(grid, r, c + 1); // 右 + dfs(grid, r + 1, c); // 下 + dfs(grid, r, c - 1); // 左 + }; + + // 如果邻接矩阵为空,或者没有任何数据,直接返回0 + if (!grid || grid.length === 0) return 0; + let nr = grid.length; // 获取行的个数 + let nc = grid[0].length; // 获取列的个数 + let lands = 0; // 岛屿数量 + + for (let r = 0; r < nr; ++r) { + for (let c = 0; c < nc; ++c) { + if (grid[r][c] === '1') { // 发现陆地,必定有一座岛,把相邻的陆地全部清空 + lands++; + dfs(grid, r, c); // 从当前点开始DFS + } + } + } + + return lands; +} + +/* +使用BFS,思路和DFS一致 +*/ +function f2(grid) { + // 获取网格的行数 + const nr = grid.length; + if (nr === 0) return 0; // 如果网格为空,返回0岛屿 + // 获取网格的列数 + const nc = grid[0].length; + + // 记录岛屿的数量 + let num_islands = 0; + + // 遍历每个格子 + for (let r = 0; r < nr; r++) { + for (let c = 0; c < nc; c++) { + // 如果当前格子是陆地('1'),说明找到了一个新的岛屿 + if (grid[r][c] === '1') { + // 增加岛屿数量 + num_islands++; + // 将当前格子标记为已访问,防止再次访问 + grid[r][c] = '0'; + + // 创建一个队列来进行BFS + const neighbors = []; + neighbors.push([r, c]); // 将当前岛屿的起始点入队列 + + // 进行BFS遍历岛屿中的所有陆地 + while (neighbors.length > 0) { + // 获取队列的第一个元素 + const [row, col] = neighbors.shift(); + + // 检查上下左右四个方向的相邻格子 + // 上 + if (row - 1 >= 0 && grid[row - 1][col] === '1') { + neighbors.push([row - 1, col]); + grid[row - 1][col] = '0'; // 将相邻陆地标记为水 + } + // 下 + if (row + 1 < nr && grid[row + 1][col] === '1') { + neighbors.push([row + 1, col]); + grid[row + 1][col] = '0'; // 将相邻陆地标记为水 + } + // 左 + if (col - 1 >= 0 && grid[row][col - 1] === '1') { + neighbors.push([row, col - 1]); + grid[row][col - 1] = '0'; // 将相邻陆地标记为水 + } + // 右 + if (col + 1 < nc && grid[row][col + 1] === '1') { + neighbors.push([row, col + 1]); + grid[row][col + 1] = '0'; // 将相邻陆地标记为水 + } + } + } + } + } + + // 返回岛屿的数量 + return num_islands; +} + +/* +利用并查集,矩阵里面所有的‘1’都认为是一个小岛屿,用一个一维数组parent表示并查集,以 grid[i][j] === '1' 这个位置为例,那么这个 +位置在并查集中的下标就是parent[i*col+j] col表示矩阵的列数,而parent[i * col + j]表示的是它的父节点,默认指向自己,定义一个遍历count +记录岛屿的数量, +*/ +// TODO: diff --git a/top-interview-leetcode150/graph/207课程表.js b/top-interview-leetcode150/graph/207课程表.js new file mode 100644 index 0000000..246446d --- /dev/null +++ b/top-interview-leetcode150/graph/207课程表.js @@ -0,0 +1,42 @@ +/** + * @param {number} numCourses + * @param {number[][]} prerequisites + * @return {boolean} + */ +const canFinish = function (numCourses, prerequisites) { + +}; + +/* +这个题目是一个典型的拓扑排序问题,只需要判断拓扑排序最终能否输出所有元素,如果可以则表明是一个有向无环图,符合 +题目要求,否则不符合 +*/ +function f1(numCourses, prerequisites) { + const graph = Array.from({ length: numCourses }, () => []); + const inDegree = Array(numCourses).fill(0); + + // 构建图和入度表 + for (const [a, b] of prerequisites) { + graph[b].push(a); + inDegree[a]++; + } + + // 找出入度为零的顶点,将其加入队列 + const queue = []; + for (let i = 0; i < numCourses; i++) { + if (inDegree[i] === 0) queue.push(i); + } + + // 从队列中输出所有入度为零的顶点,如果输出的顶点数量和课程数量一致,表明可以拓扑排序,否则有环 + let count = 0; + while (queue.length > 0) { + const node = queue.shift(); + count++; + for (const neighbor of graph[node]) { + inDegree[neighbor]--; + if (inDegree[neighbor] === 0) queue.push(neighbor); + } + } + + return count === numCourses; +} diff --git a/top-interview-leetcode150/graph/210课程表.js b/top-interview-leetcode150/graph/210课程表.js new file mode 100644 index 0000000..b2af5a1 --- /dev/null +++ b/top-interview-leetcode150/graph/210课程表.js @@ -0,0 +1,94 @@ +/** + * 课程安排 II - 拓扑排序(DFS) + * @param {number} numCourses - 课程总数 + * @param {number[][]} prerequisites - 每一对 [a, b] 表示上课程 a 需要先完成课程 b + * @return {number[]} - 返回一个可行的课程学习顺序,如果无法完成所有课程则返回 [] + */ +const findOrder = function (numCourses, prerequisites) { + return dfsTopoSort(numCourses, prerequisites); +}; + +/** + * 使用 DFS 实现拓扑排序,并判断是否存在环 + * @param {number} numCourses + * @param {number[][]} prerequisites + * @return {number[]} + */ +function dfsTopoSort(numCourses, prerequisites) { + const graph = Array.from({ length: numCourses }, () => []); // 邻接表表示图 + const visited = Array(numCourses).fill(0); // 节点状态:0=未访问,1=访问中,2=已完成 + const result = []; // 用于存放拓扑排序结果(逆序) + let hasCycle = false; // 用于标记图中是否存在环 + + // 构建图:b -> a 表示先学 b 才能学 a + for (const [a, b] of prerequisites) { + graph[b].push(a); + } + + /** + * 深度优先搜索当前节点 + * @param {number} node + */ + function dfs(node) { + if (visited[node] === 1) { + // 当前节点正在访问中,说明存在环 + hasCycle = true; + return; + } + if (visited[node] === 2 || hasCycle) { + // 节点已处理,或已发现环,直接返回 + return; + } + + visited[node] = 1; // 标记为访问中 + for (const neighbor of graph[node]) { + dfs(neighbor); + } + visited[node] = 2; // 标记为已完成 + result.push(node); // 所有邻居处理完后再加入结果,确保当前节点排在后面 + } + + // 遍历所有节点,防止图不连通 + for (let i = 0; i < numCourses; i++) { + dfs(i); + } + + // 如果存在环,则无法完成所有课程 + return hasCycle ? [] : result.reverse(); +} + +/* +使用kahn算法来实现,一个顶点能从其他顶点过来表明有一个入度,有多少个顶点能到达这个顶点就有多少个入度,这里直接copy207 +修改一下即可 +*/ +function f2(numCourses, prerequisites) { + const graph = Array.from({ length: numCourses }, () => []); + const inDegree = Array(numCourses).fill(0); + const result = []; + + // 构建图和入度表 + for (const [a, b] of prerequisites) { + graph[b].push(a); + inDegree[a]++; + } + + // 找出入度为零的顶点,将其加入队列 + const queue = []; + for (let i = 0; i < numCourses; i++) { + if (inDegree[i] === 0) queue.push(i); + } + + // 从队列中输出所有入度为零的顶点,如果输出的顶点数量和课程数量一致,表明可以拓扑排序,否则有环 + while (queue.length > 0) { + const node = queue.shift(); + // 将入度为零的顶点存入结果集合 + result.push(node); + for (const neighbor of graph[node]) { + inDegree[neighbor]--; + if (inDegree[neighbor] === 0) queue.push(neighbor); + } + } + + // 如果结果集中的顶点的数量小于numCourses那么表明有环,放回[],否则返回结果集 + return result.length < numCourses ? [] : result; +} diff --git a/top-interview-leetcode150/graph/399除法求值.js b/top-interview-leetcode150/graph/399除法求值.js new file mode 100644 index 0000000..04ba016 --- /dev/null +++ b/top-interview-leetcode150/graph/399除法求值.js @@ -0,0 +1,96 @@ +/** + * @param {string[][]} equations + * @param {number[]} values + * @param {string[][]} queries + * @return {number[]} + */ +const calcEquation = function (equations, values, queries) { + +}; + +/* +思路:通过equations和values建立一张有权图edges:[[[1, 2],...], [[2, 3],...]],在这里使用邻接表的方式表示,edges表示的是0号顶点可以到一号顶点 +并且权值为2,1顶点到2号顶点的权值是3,如果0号顶点表示a,1号顶点表示b,2号顶点表示c,那么这个图在这个题目里面的意思就是,a/b=2,b/c=2,如果要我们, +求a/c的值不就是把a->b->c这个路径里面的权值相乘吗?所以这个题目可以分为下面几步: +步骤: +1.通过equations,values建立有权图edges +2.遍历queries,比如要我们求a/e,那么我们就从a为起点出发,开始广度优先遍历,知道找到a->e的路径,之后加权(相乘),如果没找到,就是-1 +*/ +function f1(equations, values, queries) { + // Step 1: 给每个变量编号,变量名映射到编号 + let nvars = 0; // 变量的数量 + const variables = new Map(); // 存储变量名到编号的映射 + + // 处理 equations,给每个变量一个唯一的编号 + const n = equations.length; + for (let i = 0; i < n; i++) { + // 如果变量没有编号,就给它一个编号 + if (!variables.has(equations[i][0])) { + variables.set(equations[i][0], nvars++); + } + if (!variables.has(equations[i][1])) { + variables.set(equations[i][1], nvars++); + } + } + + // Step 2: 创建图结构,存储每个节点与其他节点的比值 + const edges = new Array(nvars).fill(0); // 创建一个数组来存储每个节点的邻接点和比值 + for (let i = 0; i < nvars; i++) { + edges[i] = []; // 初始化每个节点的邻接表 + } + + // 遍历 equations,构建图,每条边的权值是它们之间的比值 + for (let i = 0; i < n; i++) { + const va = variables.get(equations[i][0]); // 获取变量 a 的编号 + const vb = variables.get(equations[i][1]); // 获取变量 b 的编号 + edges[va].push([vb, values[i]]); // a → b 的比值 + edges[vb].push([va, 1.0 / values[i]]); // b → a 的比值是 1 / (a → b) + } + + // Step 3: 处理查询,计算每个查询的结果 + const queriesCount = queries.length; // 查询的数量 + const ret = []; // 存储每个查询的结果 + + for (let i = 0; i < queriesCount; i++) { + const query = queries[i]; // 当前查询 + let result = -1.0; // 默认结果为 -1.0,表示无法计算 + + // 只有当查询中的两个变量都存在时,才进行计算 + if (variables.has(query[0]) && variables.has(query[1])) { + const ia = variables.get(query[0]); // 获取查询中第一个变量的编号 + const ib = variables.get(query[1]); // 获取查询中第二个变量的编号 + + // 如果两个变量是同一个,直接返回 1.0 + if (ia === ib) { + result = 1.0; + } else { + // Step 4: 使用 DFS 或 BFS 遍历图,查找从 ia 到 ib 的比值 + const points = []; // 存储当前正在遍历的节点 + points.push(ia); // 从 ia 开始遍历 + + const ratios = new Array(nvars).fill(-1.0); // 存储每个节点的比值,初始值为 -1.0,表示不可达 + ratios[ia] = 1.0; // 起点 ia 到 ia 的比值是 1.0 + + // 使用 DFS 方式遍历图 + while (points.length && ratios[ib] < 0) { + const x = points.pop(); // 从栈中取出一个节点 + // 遍历当前节点的所有邻接点 + for (const [y, val] of edges[x]) { + // 如果邻接点 y 尚未访问过(ratios[y] < 0) + if (ratios[y] < 0) { + ratios[y] = ratios[x] * val; // 更新 y 的比值 + points.push(y); // 将 y 加入栈中继续遍历 + } + } + } + result = ratios[ib]; // 查询的结果是 ib 到达的比值 + } + } + + // 将当前查询的结果存入结果数组 + ret[i] = result; + } + + // 返回所有查询的结果 + return ret; +} diff --git a/top-interview-leetcode150/graph/909蛇梯棋子.js b/top-interview-leetcode150/graph/909蛇梯棋子.js new file mode 100644 index 0000000..5d11c8f --- /dev/null +++ b/top-interview-leetcode150/graph/909蛇梯棋子.js @@ -0,0 +1,129 @@ +/** + * @param {number[][]} board + * @return {number} + */ +const snakesAndLadders = function (board) { + +}; + +/* +/* +把棋盘的每一个位置看成图的一个顶点,每一个顶点都有六个有向边指向后面的六个位置,也就是后面的六个顶点,有的顶点比较特殊, +它不指向后面的六个顶点,而是指向其他的顶点,我们要求的就是到达最后一个位置的顶点所需的最少步数,到这里我们很容易发现,这 +是一个图的BFS题目,我们从最初的位置一层一层的往外扩,需要用几层就是几步, +*/ +function f1(board) { + // 定义一个长度为n*n+1的一维数组,将board压缩,减少计数难度 + const n = board.length; + const size = n * n; + const arr = new Array(size + 1); // 0位置不需要,这样就能和board一一对应 + let idx = 1; // 棋盘初始位置 + let leftToRight = true; // s行遍历board + + for (let i = n - 1; i >= 0; i--) { + if (leftToRight) { + for (let j = 0; j < n; j++) { + arr[idx++] = board[i][j]; + } + } else { + for (let j = n - 1; j >= 0; j--) { + arr[idx++] = board[i][j]; + } + } + leftToRight = !leftToRight; + } + + // bfs队列,从第一个位置开始,[1,0]表示,到达第一个位置最少只需要一步 + const queue = [[1, 0]]; + const visited = new Array(size + 1).fill(false); // 防止每一层的元素重复加入到下一层 + visited[1] = true; + while (queue.length) { + const [cur, step] = queue.shift(); + + // 查看邻接的六个顶点 + for (let i = 1; i <= 6; i++) { + let next = cur + i; // 下一个顶点位置 + + // 越界,忽略这个顶点 + if (next > size) { + continue; + } + + // 如果遇到蛇和梯子立即调整指定位置 + if (arr[next] !== -1) { + next = arr[next]; + } + + // 如果邻接顶点就是目标位置,直接放回到达当前顶点的步数在加1 + if (next === size) { + return step + 1; + } + + // 如果元素没有被访问将其加入队列,扩展下一层 + if (!visited[next]) { + visited[next] = true; + queue.push([next, step + 1]); + } + } + } + return -1; // 不可达 +} + +/* +将二维数组变成一维数组需要log(n*n)的时间复杂度,n为棋牌大小,相等于一开始就遍历了整个棋盘一遍,如果能实时计算当前位置,到其他位置 +在board中的位置可以大大减少时间复杂度。 +思考:假设我们有一个大小为n的棋牌,计数位置place(1 ~ n*n)在board中的坐标,首先计算行,行非常好计算,r = (place - 1) / n,那么列 +还是 (place-1) % n吗?在这个题目不是的因为棋牌是按照s行走的,偶数行从左往右,奇数行从右往左,偶数行显然是(place-1)%n,那么技术行只需要将n +减去它即可 +*/ + +const id2rc = (id, n) => { + const r = Math.floor((id - 1) / n); + let c = (id - 1) % n; + if (r % 2 === 1) { + c = n - 1 - c; + } + return [n - 1 - r, c]; +}; + +function f2(board) { + const n = board.length; + const size = n * n; + + // BFS + const queue = [[1, 0]]; + const visited = new Array(size + 1).fill(false); // 防止每一层的元素重复加入到下一层 + visited[1] = true; + + while (queue.length) { + const [cur, step] = queue.shift(); + + // 查看邻接的六个顶点 + for (let i = 1; i <= 6; i++) { + let next = cur + i; // 下一个顶点位置 + + // 越界,忽略这个顶点 + if (next > size) { + continue; + } + + const [r, c] = id2rc(next, n); + // 如果遇到蛇和梯子立即调整指定位置 + if (board[r][c] !== -1) { + next = board[r][c]; + } + + // 如果邻接顶点就是目标位置,直接放回到达当前顶点的步数在加1 + if (next === size) { + return step + 1; + } + + // 如果元素没有被访问将其加入队列,扩展下一层 + if (!visited[next]) { + visited[next] = true; + queue.push([next, step + 1]); + } + } + } + return -1; // 不可达 +}