题目

给你一个下标从 0 开始的二维整数数组 grid ,数组大小为 m x n 。每个单元格都是两个值之一:

0 表示一个 空 单元格,
1 表示一个可以移除的 障碍物 。
你可以向上、下、左、右移动,从一个空单元格移动到另一个空单元格。

现在你需要从左上角 (0, 0) 移动到右下角 (m - 1, n - 1) ,返回需要移除的障碍物的 最小 数目。

示例 1:

输入:grid = [[0,1,1],[1,1,0],[1,1,0]]
输出:2
解释:可以移除位于 (0, 1) 和 (0, 2) 的障碍物来创建从 (0, 0) 到 (2, 2) 的路径。
可以证明我们至少需要移除两个障碍物,所以返回 2 。
注意,可能存在其他方式来移除 2 个障碍物,创建出可行的路径。

示例 2:

输入:grid = [[0,1,0,0,0],[0,1,0,1,0],[0,0,0,1,0]]
输出:0
解释:不移除任何障碍物就能从 (0, 0) 到 (2, 4) ,所以返回 0 。

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 10^5
2 <= m * n <= 10^5
grid[i][j] 为 0 或 1
grid[0][0] == grid[m - 1][n - 1] == 0

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/minimum-obstacle-removal-to-reach-corner
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

可算借这个题把dijkstra最短路算法研究懂了,不过着实有点慢。对于本题还有更快的01BFS,待补充

代码

dijkstra

  1. class Solution {
  2. public int minimumObstacles(int[][] grid) {
  3. int m = grid.length;
  4. int n = grid[0].length;
  5. int[][] dirs = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
  6. Graph graph = new Graph(m * n);
  7. for (int i = 0; i < m; i++) {
  8. for (int j = 0; j < n; j++) {
  9. for (int[] dir : dirs) {
  10. int nx = i + dir[0];
  11. int ny = j + dir[1];
  12. if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
  13. graph.addEdge(i * n + j, nx * n + ny, grid[nx][ny]);
  14. }
  15. }
  16. }
  17. }
  18. return graph.dijkstra(0, m * n - 1);
  19. }
  20. }
  21. class Graph { // 有向有权图的邻接表表示
  22. private LinkedList<Edge> adj[]; // 邻接表
  23. private int v; // 顶点个数
  24. public Graph(int v) {
  25. this.v = v;
  26. this.adj = new LinkedList[v];
  27. for (int i = 0; i < v; ++i) {
  28. this.adj[i] = new LinkedList<>();
  29. }
  30. }
  31. // 添加一条边
  32. public void addEdge(int s, int t, int w) {
  33. this.adj[s].add(new Edge(s, t, w));
  34. }
  35. private class Edge {
  36. public int sid; // 边的起始顶点编号
  37. public int tid; // 边的终止顶点编号
  38. public int w; // 权重
  39. public Edge(int sid, int tid, int w) {
  40. this.sid = sid;
  41. this.tid = tid;
  42. this.w = w;
  43. }
  44. }
  45. // 下面这个类是为了dijkstra实现用的
  46. private class Vertex {
  47. public int id; // 顶点编号ID
  48. public int dist; // 从起始顶点到这个顶点的距离
  49. public Vertex(int id, int dist) {
  50. this.id = id;
  51. this.dist = dist;
  52. }
  53. }
  54. // 因为Java提供的优先级队列,没有暴露更新数据的接口,所以我们需要重新实现一个
  55. // 根据vertex.dist构建小顶堆
  56. private class PriorityQueue {
  57. private Vertex[] nodes;
  58. private int count;
  59. private int n;
  60. public PriorityQueue(int v) {
  61. this.nodes = new Vertex[v + 1];
  62. this.count = 0;
  63. this.n = v;
  64. }
  65. public Vertex poll() {
  66. if (count == 0) return null;
  67. Vertex top = nodes[1];
  68. nodes[1] = nodes[count];
  69. --count;
  70. heapify(nodes, count, 1);
  71. return top;
  72. }
  73. private void heapify(Vertex[] nodes, int n, int i) { // 自上往下堆化
  74. while (true) {
  75. int minPos = i;
  76. if (i * 2 <= n && nodes[i * 2] != null && nodes[i].dist > nodes[i * 2].dist) {
  77. minPos = i * 2;
  78. }
  79. if (i * 2 + 1 <= n && nodes[i * 2 + 1] != null && nodes[minPos].dist > nodes[i * 2 + 1].dist) {
  80. minPos = i * 2 + 1;
  81. }
  82. if (minPos == i) break;
  83. swap(nodes, i, minPos);
  84. i = minPos;
  85. }
  86. }
  87. private void swap(Vertex[] nodes, int i, int j) {
  88. Vertex tmp = nodes[i];
  89. nodes[i] = nodes[j];
  90. nodes[j] = tmp;
  91. }
  92. public void add(Vertex vertex) {
  93. if (count >= n) return; // 堆满了
  94. ++count;
  95. nodes[count] = vertex;
  96. int i = count;
  97. while (i / 2 > 0 && nodes[i].dist < nodes[i / 2].dist) { // 自下往上堆化
  98. swap(nodes, i, i / 2);
  99. i = i / 2;
  100. }
  101. }
  102. // 更新结点的值,并且从下往上堆化,重新符合堆的定义。时间复杂度O(logn)。
  103. public void update(Vertex vertex) {
  104. nodes[vertex.id].dist = vertex.dist;
  105. int i = vertex.id;
  106. while (i / 2 > 0 && nodes[i].dist < nodes[i / 2].dist) { // 自下往上堆化
  107. swap(nodes, i, i / 2);
  108. i = i / 2;
  109. }
  110. }
  111. public boolean isEmpty() {
  112. return count == 0;
  113. }
  114. }
  115. public int dijkstra(int s, int t) { // 从顶点s到顶点t的最短路径
  116. Vertex[] vertexes = new Vertex[this.v];
  117. for (int i = 0; i < this.v; ++i) {
  118. vertexes[i] = new Vertex(i, Integer.MAX_VALUE);
  119. }
  120. PriorityQueue q = new PriorityQueue(this.v);// 小顶堆
  121. boolean[] inqueue = new boolean[this.v]; // 标记是否进入过队列
  122. vertexes[s].dist = 0;
  123. q.add(vertexes[s]);
  124. inqueue[s] = true;
  125. while (!q.isEmpty()) {
  126. Vertex minVertex = q.poll(); // 取堆顶元素并删除
  127. if (minVertex.id == t) return minVertex.dist; // 最短路径产生了
  128. for (int i = 0; i < adj[minVertex.id].size(); ++i) {
  129. Edge e = adj[minVertex.id].get(i); // 取出一条minVetex相连的边
  130. Vertex nextVertex = vertexes[e.tid]; // minVertex-->nextVertex
  131. if (minVertex.dist + e.w < nextVertex.dist) { // 更新next的dist
  132. nextVertex.dist = minVertex.dist + e.w;
  133. if (inqueue[nextVertex.id]) {
  134. q.update(nextVertex); // 更新队列中的dist值
  135. } else {
  136. q.add(nextVertex);
  137. inqueue[nextVertex.id] = true;
  138. }
  139. }
  140. }
  141. }
  142. return -1;
  143. }
  144. }