|
2 | 2 |
|
3 | 3 | 这是 LeetCode 上的 **[675. 为高尔夫比赛砍树](https://leetcode.cn/problems/cut-off-trees-for-golf-event/solution/by-ac_oier-ksth/)** ,难度为 **困难**。 |
4 | 4 |
|
5 | | -Tag : 「图论 BFS」、「AStar 算法」、「启发式搜索」 |
| 5 | +Tag : 「图论 BFS」、「AStar 算法」、「启发式搜索」、「并查集」 |
6 | 6 |
|
7 | 7 |
|
8 | 8 |
|
@@ -221,6 +221,102 @@ class Solution { |
221 | 221 |
|
222 | 222 | --- |
223 | 223 |
|
| 224 | +### AStar 算法 + 并查集预处理无解 |
| 225 | + |
| 226 | +我们知道,AStar 算法使用到了「优先队列(堆)」来进行启发式搜索,而对于一些最佳路径方向与两点相对位置相反(例如 $T$ 在 $S$ 的右边,但由于存在障碍,最短路径需要先从左边绕一圈才能到 $T$),AStar 反而会因为优先队列(堆)而多一个 $\log$ 的复杂度。 |
| 227 | + |
| 228 | +因此一个可行的优化是,我们先提前处理「无解」的情况,常见的做法是在预处理过程中运用「并查集」来维护连通性。 |
| 229 | + |
| 230 | +这种对于不影响复杂度上界的预处理相比后续可能出现的大量无效搜索(最终无解)的计算量而言,是有益的。 |
| 231 | + |
| 232 | +代码: |
| 233 | +```Java |
| 234 | +class Solution { |
| 235 | + int N = 50; |
| 236 | + int[][] g = new int[N][N]; |
| 237 | + int n, m; |
| 238 | + int[] p = new int[N * N + 10]; |
| 239 | + List<int[]> list = new ArrayList<>(); |
| 240 | + void union(int a, int b) { |
| 241 | + p[find(a)] = p[find(b)]; |
| 242 | + } |
| 243 | + boolean query(int a, int b) { |
| 244 | + return find(a) == find(b); |
| 245 | + } |
| 246 | + int find(int x) { |
| 247 | + if (p[x] != x) p[x] = find(p[x]); |
| 248 | + return p[x]; |
| 249 | + } |
| 250 | + int getIdx(int x, int y) { |
| 251 | + return x * m + y; |
| 252 | + } |
| 253 | + public int cutOffTree(List<List<Integer>> forest) { |
| 254 | + n = forest.size(); m = forest.get(0).size(); |
| 255 | + // 预处理过程中,同时使用「并查集」维护连通性 |
| 256 | + for (int i = 0; i < n * m; i++) p[i] = i; |
| 257 | + int[][] tempDirs = new int[][]{{0,-1},{-1,0}}; |
| 258 | + for (int i = 0; i < n; i++) { |
| 259 | + for (int j = 0; j < m; j++) { |
| 260 | + g[i][j] = forest.get(i).get(j); |
| 261 | + if (g[i][j] > 1) list.add(new int[]{g[i][j], i, j}); |
| 262 | + if (g[i][j] == 0) continue; |
| 263 | + // 只与左方和上方的区域联通即可确保不重不漏 |
| 264 | + for (int[] di : tempDirs) { |
| 265 | + int nx = i + di[0], ny = j + di[1]; |
| 266 | + if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; |
| 267 | + if (g[nx][ny] != 0) union(getIdx(i, j), getIdx(nx, ny)); |
| 268 | + } |
| 269 | + } |
| 270 | + } |
| 271 | + // 若不满足所有树点均与 (0,0),提前返回无解 |
| 272 | + for (int[] info : list) { |
| 273 | + int x = info[1], y = info[2]; |
| 274 | + if (!query(getIdx(0, 0), getIdx(x, y))) return -1; |
| 275 | + } |
| 276 | + Collections.sort(list, (a,b)->a[0]-b[0]); |
| 277 | + int x = 0, y = 0, ans = 0; |
| 278 | + for (int[] ne : list) { |
| 279 | + int nx = ne[1], ny = ne[2]; |
| 280 | + int d = astar(x, y, nx, ny); |
| 281 | + if (d == -1) return -1; |
| 282 | + ans += d; |
| 283 | + x = nx; y = ny; |
| 284 | + } |
| 285 | + return ans; |
| 286 | + } |
| 287 | + int f(int X, int Y, int P, int Q) { |
| 288 | + return Math.abs(X - P) + Math.abs(Y - Q); |
| 289 | + } |
| 290 | + int[][] dirs = new int[][]{{0,1},{0,-1},{1,0},{-1,0}}; |
| 291 | + int astar(int X, int Y, int P, int Q) { |
| 292 | + if (X == P && Y == Q) return 0; |
| 293 | + Map<Integer, Integer> map = new HashMap<>(); |
| 294 | + PriorityQueue<int[]> q = new PriorityQueue<>((a,b)->a[0]-b[0]); |
| 295 | + q.add(new int[]{f(X, Y, P, Q), X, Y}); |
| 296 | + map.put(getIdx(X, Y), 0); |
| 297 | + while (!q.isEmpty()) { |
| 298 | + int[] info = q.poll(); |
| 299 | + int x = info[1], y = info[2], step = map.get(getIdx(x, y)); |
| 300 | + for (int[] di : dirs) { |
| 301 | + int nx = x + di[0], ny = y + di[1], nidx = getIdx(nx, ny); |
| 302 | + if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; |
| 303 | + if (g[nx][ny] == 0) continue; |
| 304 | + if (nx == P && ny == Q) return step + 1; |
| 305 | + if (!map.containsKey(nidx) || map.get(nidx) > step + 1) { |
| 306 | + q.add(new int[]{step + 1 + f(nx, ny, P, Q), nx, ny}); |
| 307 | + map.put(nidx, step + 1); |
| 308 | + } |
| 309 | + } |
| 310 | + } |
| 311 | + return -1; |
| 312 | + } |
| 313 | +} |
| 314 | +``` |
| 315 | +* 时间复杂度:启发式搜索分析时空复杂度意义不大 |
| 316 | +* 空间复杂度:启发式搜索分析时空复杂度意义不大 |
| 317 | + |
| 318 | +--- |
| 319 | + |
224 | 320 | ### 最后 |
225 | 321 |
|
226 | 322 | 这是我们「刷穿 LeetCode」系列文章的第 `No.675` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
|
0 commit comments