1631.最小体力消耗路径

题目

你准备参加一场远足活动。给你一个二维 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 。

题解

1. BFS
  1. 类似于最短路径
  2. 在BFS的同时去记录到当前坐标所需要消耗的体力值,由于到达一个坐标有多种方式,故我们用一个数组res去记录到达当前坐标所需的最小体力值
    1. 初始化队列
    2. 访问队头元素
    3. 对于当前元素进行广度优先搜索,计算当前访问点与cur与相邻点next之间的差值是否大于起点到next的最小消耗体力值
    4. 若小于,则更新res中起点到达next所需要的最小体力值
    5. 遍历完成后,返回res[row-1][col-1]则表示起点到终点所需的最小体力值
参考代码
class Solution {
    public int minimumEffortPath(int[][] heights) {
      int row = heights.length, col = heights[0].length;
      int[] dx = {1, -1, 0, 0};
      int[] dy = {0, 0, 1, -1};

      PriorityQueue<int[]> priQueue = new PriorityQueue<int[]>(new Comparator<int[]>() {
        public int compare(int[] o1, int[] o2) {
          return o1[2] - o2[2];
        }
      });
      priQueue.offer(new int[]{0, 0, 0});

      int[][] res = new int[row][col];
      for(int[] cur : res) {
        Arrays.fill(cur, Integer.MAX_VALUE);
      }
      res[0][0] = 0;

      boolean[][] visited = new boolean[row][col];

      while(!priQueue.isEmpty()) {
        int[] cur = priQueue.poll();
        int x = cur[0];
        int y = cur[1];
        if(visited[x][y]) continue;
        visited[x][y] = true;
        // BFS开始
        for(int i = 0; i < 4; i++) {
          int newX = x + dx[i];
          int newY = y + dy[i];
          if(newX >= 0 && newX < row && newY >= 0 && newY < col) {
            int newD = Math.max(Math.abs(heights[x][y] - heights[newX][newY], res[x][y]));
            res[newX][newY] = Math.min(res[newX][newY], newD);
            priQueue.offer(new int[]{newX, newY, res[newX][newY]});
          }
        }
      }

      return res[newX][newY];
    }
}
2. 并查集
  1. 把每个的格子当前一个点,则整个网格中有row*col个点,每个格子对应的值作为其两点连线的线权
  2. 这时候问题变成:在线权最小的情况下,连通起点和终点
    1. 将整个网格内的所有格子,全部转换为:两个点与其线权这种形式,并按照线权进行排序
    2. 从小开始依次遍历所有的点-边式
    3. 如果该点-边式的两个点合并为一个连通分量,并判断起点到终点是否属于一个连通分量
    4. 重复步骤3
参考代码
class Solution {
    public int minimumEffortPath(int[][] heights) {
      int row = heights.length, col = heights[0].length;
      List<int[]> edges = new ArrayList<>();
      for(int i = 0; i < row; i++) {
        for(int j = 0; j < col; j++) {
          int id = i * col + j;
          if(i > 0) {
            edges.add(new int[]{id - col, id, Math.abs(heights[i][j]-heights[i-1][j])});
          }
          if(j > 0) {
            edges.add(new int[]{id - 1, id, Math.abs(heights[i][j]-heights[i][j-1])})
          }
        }
      }

      Collections.sort(edges, new Comparator<int[]>() {
        public int compare(int[] o1, int[] o2) {
          return o1[2] - o2[2];
        }
      });

      UnionFind myUnionFind = new UnionFind(row * col);
      int ans = 0;
      for(int[] edge : edges) {
        int x = edge[0];
        int y = edge[1];
        int d = edge[2];
        myUnionFind.union(x, y);
        if(myUnionFind.isConnected(0, row*col-1)) {
          ans = d;
          break;
        }
      }

      return ans;

    }

    class UnionFind{
      int[] parent;
      int[] rank;

      public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        Arrays.fill(rank, 1);
        for(int i = 0; i < n; i++) {
          parent[i] = i;
        }
      }

      public void union(int a, int b) {
        int rootA = find(a);
        int rootB = find(b);
        if(rootA == rootB) return;
        if(rank[rootA] < rank[rootB]) {
          parent[rootA] = rootB;
        } else {
          if(rank[rootA] == rank[rootB]) {
            rank[rootA]++;
          }
          parent[rootB] = rootA;
        }
      }

      public boolean isConnected(int a, int b) {
        int rootA = find(a);
        int rootB = find(b);
        return rootA == rootB;
      }

      public int find(int index) {
        if(index != parent[index]) {
          return find(parent[index]);
        }
        return index;
      }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值