一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
说明:文章部分内容及图片出自网络,如有侵权请与我本人联系(主页有公众号:小攻城狮学前端)
作者:小只前端攻城狮、 主页:小只前端攻城狮的主页、 来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【LeetCode 1631. 最小体力消耗路径 】- JavaScript(并查集+Dijkstra)
题意描述
你准备参加一场远足活动。给你一个二维
rows x columns的地图heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往上,下,左,右四个方向之一移动,你想要找到耗费体力最小的一条路径。一条路径耗费的
体力值是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。请你返回从左上角走到右下角的最小 体力消耗值 。
示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]] 输出:2 解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。 这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
示例 2:
输入:scores = [4,5,6,5], ages = [2,1,2,1] 输出:16 解释:最佳的选择是后 3 名球员。注意,你可以选中多个同龄球员。
思路分析:
一开始还想搜索+剪枝,发现一直TLE,还以为终于不是图论变成动态规划了,仔细一看好家伙又是图论。 万万想不到这也能并查集这也能Dijkstra。敢问并查集还有什么不能做的
并查集
核心思想:
- 先构建各点之间的边列表edges。 其中由edge=[x,y,d]组成,d为点x与y差的绝对值
- 根据边列表中的 各点差的绝对值 从小到大排序
- 依次遍历edges, 并将各点进行连通, 当最左上与最左下 第一次连通时结束,并输出 所遍历过edges中的最大d
class UnionFind {
constructor(n) {
this.count = n;
this.rank = new Array(n).fill(1);
this.parents = new Array(n).fill(0).map((v, i) => i);
}
find(x) {
return this.parents[x] === x ? x : this.find(this.parents[x]);
}
union(p, q) {
const rootP = this.find(p);
const rootQ = this.find(q);
if (rootP === rootQ) {
return;
}
if (this.rank[rootP] >= this.rank[rootQ]) {
this.parents[rootQ] = this.parents[rootP];
this.rank[rootP] += this.rank[rootQ];
} else {
this.parents[rootP] = this.parents[rootQ];
this.rank[rootQ] += this.parents[rootP];
}
this.count--;
}
isConnected(p, q) {
return this.find(p) === this.find(q);
}
}
var minimumEffortPath = function(heights) {
const m = heights.length, n = heights[0].length;
const uf = new UnionFind(m * n)
const edges = [];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
const id = i * n + j;
if (i > 0) {
edges.push([id-n, id, Math.abs(heights[i][j] - heights[i-1][j])]);
}
if (j > 0) {
edges.push([id-1, id, Math.abs(heights[i][j] - heights[i][j-1])]);
}
}
}
edges.sort((a, b) => a[2] - b[2]);
for (const [x, y, diff] of edges) {
uf.union(x, y);
if (uf.isConnected(0, m * n - 1)) {
return diff;
}
}
return 0
};
Dijkstra
思路:****
- 将二维的坐标一维化:
即将(x,y)记为x*col+y; - 使用优先队列对最短路径进行排序上的控制;
- Dijkstra算法构造:这道题可以想成BFS+优先队列,当然这里Dijkstra的更新条件记得要改一下。
interface PointState {
x: number; //col
y: number; //row
heightFromStart: number;
}
function minimumEffortPath(heights: number[][]): number {
const rowCount = heights.length;
const colCount = heights[0].length;
const queue: PointState[] = [];
const dp: number[][] = [];
for(let i=0;i<rowCount;i++){
dp[i] = []
for(let j=0;j<colCount;j++){
dp[i][j] = Infinity
}
}
queue.push({x:0,y:0,heightFromStart:0});
dp[0][0] = 0;
while (queue.length > 0) {
const curPointState = queue.shift();
const {x,y} = curPointState;
if (x === colCount -1 && y === rowCount - 1){
continue;
}
if (curPointState.heightFromStart > dp[x][y]){
continue;
}
getNeighborItem(heights,x,y).forEach((child)=>{
const height = Math.abs(heights[x][y] - heights[child.x][child.y]);
const childHeight = Math.max(height,curPointState.heightFromStart);
if (childHeight < dp[child.x][child.y]){
dp[child.x][child.y] = childHeight;
queue.push({x:child.x,y:child.y,heightFromStart:childHeight})
}
})
}
return dp[rowCount - 1][colCount - 1];
};
function getNeighborItem(heights: number[][],x: number,y: number): {x: number;y:number}[] {
const result = []
const t = {x:x-1,y};
const l = {x,y:y-1};
const r = {x,y:y+1};
const b = {x:x+1,y};
function insert(p: any) {
const xValid = p.x >=0 && p.x < heights.length;
const yValid = p.y >=0 && p.y < heights[0].length;
if (xValid && yValid) result.push(p)
}
insert(t);insert(l);insert(r);insert(b);
return result;
}
感谢阅读,希望能对你有所帮助,文章若有错误或者侵权,可以在评论区留言或在我的主页添加公众号联系我。
写作不易,如果觉得不错,可以「点赞」+「评论」 谢谢支持❤