classSolution: defminimumEffortPath(self, heights: List[List[int]]) -> int: m, n = len(heights), len(heights[0]) left, right, ans = 0, 10**6 - 1, 0
while left <= right: mid = (left + right) // 2 q = collections.deque([(0, 0)]) seen = {(0, 0)} while q: x, y = q.popleft() for nx, ny in [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]: if0 <= nx < m and0 <= ny < n and (nx, ny) notin seen andabs(heights[x][y] - heights[nx][ny]) <= mid: q.append((nx, ny)) seen.add((nx, ny)) if (m - 1, n - 1) in seen: ans = mid right = mid - 1 else: left = mid + 1 return ans
intminimumEffortPath(int** heights, int heightsSize, int* heightsColSize) { int m = heightsSize; int n = heightsColSize[0]; int left = 0, right = 999999, ans = 0; while (left <= right) { int mid = (left + right) / 2; int q[n * m][2]; int qleft = 0, qright = 0; q[qright][0] = 0, q[qright++][1] = 0; int seen[m * n]; memset(seen, 0, sizeof(seen)); seen[0] = 1; while (qleft < qright) { int x = q[qleft][0], y = q[qleft++][1]; for (int i = 0; i < 4; ++i) { int nx = x + dirs[i][0]; int ny = y + dirs[i][1]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && !seen[nx * n + ny] && abs(heights[x][y] - heights[nx][ny]) <= mid) { q[qright][0] = nx, q[qright++][1] = ny; seen[nx * n + ny] = 1; } } } if (seen[m * n - 1]) { ans = mid; right = mid - 1; } else { left = mid + 1; } } return ans; }
复杂度分析
时间复杂度:O(mn \log C),其中 m 和 n 分别是地图的行数和列数,C 是格子的最大高度,在本题中 C 不超过 10^6。我们需要进行 O(\log C) 次二分查找,每一步查找的过程中需要使用广度优先搜索,在 O(mn) 的时间判断是否可以从左上角到达右下角,因此总时间复杂度为 O(mn \log C)。
空间复杂度:O(mn),即为广度优先搜索中使用的队列需要的空间。
方法二:并查集
思路与算法
我们将这 mn 个节点放入并查集中,实时维护它们的连通性。
由于我们需要找到从左上角到右下角的最短路径,因此我们可以将图中的所有边按照权值从小到大进行排序,并依次加入并查集中。当我们加入一条权值为 x 的边之后,如果左上角和右下角从非连通状态变为连通状态,那么 x 即为答案。
// 并查集模板 classUnionFind { public: vector<int> parent; vector<int> size; int n; // 当前连通分量数目 int setCount; public: UnionFind(int _n): n(_n), setCount(_n), parent(_n), size(_n, 1) { iota(parent.begin(), parent.end(), 0); } intfindset(int x){ return parent[x] == x ? x : parent[x] = findset(parent[x]); } boolunite(int x, int y){ x = findset(x); y = findset(y); if (x == y) { returnfalse; } if (size[x] < size[y]) { swap(x, y); } parent[y] = x; size[x] += size[y]; --setCount; returntrue; } boolconnected(int x, int y){ x = findset(x); y = findset(y); return x == y; } };
classSolution { public: intminimumEffortPath(vector<vector<int>>& heights){ int m = heights.size(); int n = heights[0].size(); vector<tuple<int, int, int>> edges; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int id = i * n + j; if (i > 0) { edges.emplace_back(id - n, id, abs(heights[i][j] - heights[i - 1][j])); } if (j > 0) { edges.emplace_back(id - 1, id, abs(heights[i][j] - heights[i][j - 1])); } } } sort(edges.begin(), edges.end(), [](constauto& e1, constauto& e2) { auto&& [x1, y1, v1] = e1; auto&& [x2, y2, v2] = e2; return v1 < v2; });
UnionFind uf(m * n); int ans = 0; for (constauto [x, y, v]: edges) { uf.unite(x, y); if (uf.connected(0, m * n - 1)) { ans = v; break; } } return ans; } };
var minimumEffortPath = function(heights) { const m = heights.length; const n = heights[0].length; 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]);
const uf = newUnionFind(m * n); let ans = 0; for (const edge of edges) { const x = edge[0], y = edge[1], v = edge[2]; uf.unite(x, y); if (uf.connected(0, m * n - 1)) { ans = v; break; } } return ans; };
voidswap(int* a, int* b) { int tmp = *a; *a = *b, *b = tmp; }
structDisjointSetUnion { int *f, *size; int n, setCount; };
voidinitDSU(struct DisjointSetUnion* obj, int n) { obj->f = malloc(sizeof(int) * n); obj->size = malloc(sizeof(int) * n); obj->n = n; obj->setCount = n; for (int i = 0; i < n; i++) { obj->f[i] = i; obj->size[i] = 1; } }
intfind(struct DisjointSetUnion* obj, int x) { return obj->f[x] == x ? x : (obj->f[x] = find(obj, obj->f[x])); }
intunionSet(struct DisjointSetUnion* obj, int x, int y) { int fx = find(obj, x), fy = find(obj, y); if (fx == fy) { returnfalse; } if (obj->size[fx] < obj->size[fy]) { swap(&fx, &fy); } obj->size[fx] += obj->size[fy]; obj->f[fy] = fx; obj->setCount--; returntrue; }
intconnected(struct DisjointSetUnion* obj, int x, int y) { return find(obj, x) == find(obj, y); }
structTuple { int x, y, z };
intcmp(conststruct Tuple* a, conststruct Tuple* b) { return a->z - b->z; }
intminimumEffortPath(int** heights, int heightsSize, int* heightsColSize) { int m = heightsSize; int n = heightsColSize[0]; structTupleedges[n * m * 2]; int edgesSize = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int id = i * n + j; if (i > 0) { edges[edgesSize].x = id - n; edges[edgesSize].y = id; edges[edgesSize++].z = fabs(heights[i][j] - heights[i - 1][j]); } if (j > 0) { edges[edgesSize].x = id - 1; edges[edgesSize].y = id; edges[edgesSize++].z = fabs(heights[i][j] - heights[i][j - 1]); } } } qsort(edges, edgesSize, sizeof(struct Tuple), cmp);
structDisjointSetUnion* uf =malloc(sizeof(struct DisjointSetUnion)); initDSU(uf, m * n); int ans = 0; for (int i = 0; i < edgesSize; i++) { unionSet(uf, edges[i].x, edges[i].y); if (connected(uf, 0, m * n - 1)) { ans = edges[i].z; break; } } return ans; }
复杂度分析
时间复杂度:O(mn \log(mn)),其中 m 和 n 分别是地图的行数和列数。图中的边数为 O(mn),因此排序的时间复杂度为 O(mn \log (mn))。并查集的时间复杂度为 O(mn \cdot \alpha(mn)),其中 \alpha 为阿克曼函数的反函数。由于后者在渐进意义下小于前者,因此总时间复杂度为 O(mn \log(mn))。
while (!q.empty()) { auto [x, y, d] = q.top(); q.pop(); int id = x * n + y; if (seen[id]) { continue; } if (x == m - 1 && y == n - 1) { break; } seen[id] = 1; for (int i = 0; i < 4; ++i) { int nx = x + dirs[i][0]; int ny = y + dirs[i][1]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && max(d, abs(heights[x][y] - heights[nx][ny])) < dist[nx * n + ny]) { dist[nx * n + ny] = max(d, abs(heights[x][y] - heights[nx][ny])); q.emplace(nx, ny, dist[nx * n + ny]); } } } return dist[m * n - 1]; } };
while (!pq.isEmpty()) { int[] edge = pq.poll(); intx= edge[0], y = edge[1], d = edge[2]; intid= x * n + y; if (seen[id]) { continue; } if (x == m - 1 && y == n - 1) { break; } seen[id] = true; for (inti=0; i < 4; ++i) { intnx= x + dirs[i][0]; intny= y + dirs[i][1]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && Math.max(d, Math.abs(heights[x][y] - heights[nx][ny])) < dist[nx * n + ny]) { dist[nx * n + ny] = Math.max(d, Math.abs(heights[x][y] - heights[nx][ny])); pq.offer(newint[]{nx, ny, dist[nx * n + ny]}); } } } return dist[m * n - 1]; } }
classSolution: defminimumEffortPath(self, heights: List[List[int]]) -> int: m, n = len(heights), len(heights[0]) q = [(0, 0, 0)] dist = [0] + [float("inf")] * (m * n - 1) seen = set()
while q: d, x, y = heapq.heappop(q) iden = x * n + y if iden in seen: continue if (x, y) == (m - 1, n - 1): break seen.add(iden) for nx, ny in [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]: if0 <= nx < m and0 <= ny < n andmax(d, abs(heights[x][y] - heights[nx][ny])) <= dist[nx * n + ny]: dist[nx * n + ny] = max(d, abs(heights[x][y] - heights[nx][ny])) heapq.heappush(q, (dist[nx * n + ny], nx, ny)) return dist[m * n - 1]
type point struct{ x, y, maxDiff int } type hp []point func(h hp) Len() int { returnlen(h) } func(h hp) Less(i, j int) bool { return h[i].maxDiff < h[j].maxDiff } func(h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func(h *hp) Push(v interface{}) { *h = append(*h, v.(point)) } func(h *hp) Pop() (v interface{}) { a := *h; *h, v = a[:len(a)-1], a[len(a)-1]; return }
type pair struct{ x, y int } var dir4 = []pair{ {-1, 0}, {1, 0}, {0, -1}, {0, 1} }
funcminimumEffortPath(heights [][]int)int { n, m := len(heights), len(heights[0]) maxDiff := make([][]int, n) for i := range maxDiff { maxDiff[i] = make([]int, m) for j := range maxDiff[i] { maxDiff[i][j] = math.MaxInt64 } } maxDiff[0][0] = 0 h := &hp{ {} } for { p := heap.Pop(h).(point) if p.x == n-1 && p.y == m-1 { return p.maxDiff } if maxDiff[p.x][p.y] < p.maxDiff { continue } for _, d := range dir4 { if x, y := p.x+d.x, p.y+d.y; 0 <= x && x < n && 0 <= y && y < m { if diff := max(p.maxDiff, abs(heights[x][y]-heights[p.x][p.y])); diff < maxDiff[x][y] { maxDiff[x][y] = diff heap.Push(h, point{x, y, diff}) } } } } }
funcmax(a, b int)int { if a > b { return a } return b }
funcabs(x int)int { if x < 0 { return -x } return x }
复杂度分析
时间复杂度:O(mn \log(mn)),其中 m 和 n 分别是地图的行数和列数。对于节点数为 n_0,边数为 m_0 的图,使用优先队列优化的 Dijkstra 算法的时间复杂度为 O(m_0 \log m_0)。由于图中的边数为 O(mn),带入即可得到时间复杂度 O(mn \log(mn))。