feat: 图算法 130,133,200,207,210,399,909
This commit is contained in:
parent
0a41e81e58
commit
f53e76dcf0
123
top-interview-leetcode150/graph/130被围绕的区域.js
Normal file
123
top-interview-leetcode150/graph/130被围绕的区域.js
Normal file
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
85
top-interview-leetcode150/graph/133克隆图.js
Normal file
85
top-interview-leetcode150/graph/133克隆图.js
Normal file
@ -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;
|
||||
}
|
116
top-interview-leetcode150/graph/200岛屿的数量.js
Normal file
116
top-interview-leetcode150/graph/200岛屿的数量.js
Normal file
@ -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:
|
42
top-interview-leetcode150/graph/207课程表.js
Normal file
42
top-interview-leetcode150/graph/207课程表.js
Normal file
@ -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;
|
||||
}
|
94
top-interview-leetcode150/graph/210课程表.js
Normal file
94
top-interview-leetcode150/graph/210课程表.js
Normal file
@ -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;
|
||||
}
|
96
top-interview-leetcode150/graph/399除法求值.js
Normal file
96
top-interview-leetcode150/graph/399除法求值.js
Normal file
@ -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;
|
||||
}
|
129
top-interview-leetcode150/graph/909蛇梯棋子.js
Normal file
129
top-interview-leetcode150/graph/909蛇梯棋子.js
Normal file
@ -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; // 不可达
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user