Hard

题目描述

在一个 1 百万 x 1 百万的 XY 平面网格中,每个网格正方形的坐标为 (x, y)。

我们从起点 source = [sx, sy] 开始,想要到达目标 target = [tx, ty]。还有一个阻塞正方形数组 blocked,其中 blocked[i] = [xi, yi] 表示坐标为 (xi, yi) 的被阻塞的正方形。

每次移动,我们可以向北、东、南、西四个方向走一个正方形,前提是该正方形不在 blocked 数组中。我们也不允许走出网格。

当且仅当可以通过一系列有效的移动从起点方格到达目标方格时,返回 true。

示例 1:

输入:blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]
输出:false
解释:无法从起点到达目标,因为我们无法移动。
我们不能向北或向东移动,因为那些正方形被阻塞了。
我们不能向南或向西移动,因为我们不能走出网格。

示例 2:

输入:blocked = [], source = [0,0], target = [999999,999999]
输出:true
解释:因为没有被阻塞的格子,所以可以到达目标正方形。

约束条件:

  • 0 <= blocked.length <= 200
  • blocked[i].length == 2
  • 0 <= xi, yi < 10^6
  • source.length == target.length == 2
  • 0 <= sx, sy, tx, ty < 10^6
  • source != target
  • 保证 source 和 target 不被阻塞

解题思路

这是一个在巨大网格中的路径搜索问题,关键洞察是:如果被阻塞点形成了包围圈,那么最多只能围困有限个格子

核心思路

  1. 阻塞上限分析:最多200个阻塞点,理论上能形成的最大包围区域是当阻塞点排成直角三角形时,最多包围约 200×199/2 ≈ 20000 个格子。

  2. 双向BFS策略

    • 从起点开始BFS,如果能访问超过20000个格子,说明起点没被围困
    • 从终点开始BFS,如果能访问超过20000个格子,说明终点没被围困
    • 如果两个BFS都没被围困,或者其中一个BFS找到了对方,则返回true
  3. 优化技巧

    • 使用set快速判断格子是否被阻塞
    • BFS时记录访问过的格子数量,超过阈值就提前退出
    • 双向搜索可以减少搜索空间

算法步骤

  1. 将阻塞点转换为set以便快速查找
  2. 定义BFS函数,返回是否能访问足够多格子或找到目标
  3. 分别从起点和终点开始BFS
  4. 如果两次BFS都成功(没被围困或找到对方),返回true

这种方法巧妙地避免了在巨大网格上进行完整路径搜索的性能问题。

代码实现

class Solution {
public:
    bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {
        if (blocked.empty()) return true;
        
        set<pair<int, int>> blockSet;
        for (auto& block : blocked) {
            blockSet.insert({block[0], block[1]});
        }
        
        // 最大可能的围困区域大小
        int maxBlocked = blocked.size() * (blocked.size() - 1) / 2;
        
        return bfs(blockSet, source, target, maxBlocked) && 
               bfs(blockSet, target, source, maxBlocked);
    }
    
private:
    bool bfs(set<pair<int, int>>& blockSet, vector<int>& start, vector<int>& end, int maxBlocked) {
        queue<pair<int, int>> q;
        set<pair<int, int>> visited;
        
        q.push({start[0], start[1]});
        visited.insert({start[0], start[1]});
        
        vector<vector<int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        
        while (!q.empty() && visited.size() <= maxBlocked) {
            auto [x, y] = q.front();
            q.pop();
            
            // 找到目标
            if (x == end[0] && y == end[1]) {
                return true;
            }
            
            for (auto& dir : dirs) {
                int nx = x + dir[0], ny = y + dir[1];
                
                // 检查边界和阻塞
                if (nx < 0 || nx >= 1000000 || ny < 0 || ny >= 1000000 ||
                    blockSet.count({nx, ny}) || visited.count({nx, ny})) {
                    continue;
                }
                
                q.push({nx, ny});
                visited.insert({nx, ny});
            }
        }
        
        // 如果访问的格子数超过最大围困数,说明没被围困
        return visited.size() > maxBlocked;
    }
};
class Solution:
    def isEscapePossible(self, blocked: List[List[int]], source: List[int], target: List[int]) -> bool:
        if not blocked:
            return True
        
        block_set = set(map(tuple, blocked))
        max_blocked = len(blocked) * (len(blocked) - 1) // 2
        
        def bfs(start, end):
            queue = deque([start])
            visited = {tuple(start)}
            
            while queue and len(visited) <= max_blocked:
                x, y = queue.popleft()
                
                if [x, y] == end:
                    return True
                
                for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                    nx, ny = x + dx, y + dy
                    
                    if (0 <= nx < 1000000 and 0 <= ny < 1000000 and
                        (nx, ny) not in block_set and (nx, ny) not in visited):
                        queue.append([nx, ny])
                        visited.add((nx, ny))
            
            return len(visited) > max_blocked
        
        return bfs(source, target) and bfs(target, source)
public class Solution {
    public bool IsEscapePossible(int[][] blocked, int[] source, int[] target) {
        if (blocked.Length == 0) return true;
        
        HashSet<(int, int)> blockSet = new HashSet<(int, int)>();
        foreach (var block in blocked) {
            blockSet.Add((block[0], block[1]));
        }
        
        int maxBlocked = blocked.Length * (blocked.Length - 1) / 2;
        
        return BFS(blockSet, source, target, maxBlocked) && 
               BFS(blockSet, target, source, maxBlocked);
    }
    
    private bool BFS(HashSet<(int, int)> blockSet, int[] start, int[] end, int maxBlocked) {
        Queue<(int, int)> queue = new Queue<(int, int)>();
        HashSet<(int, int)> visited = new HashSet<(int, int)>();
        
        queue.Enqueue((start[0], start[1]));
        visited.Add((start[0], start[1]));
        
        int[][] dirs = new int[][] { new int[] {0, 1}, new int[] {0, -1}, new int[] {1, 0}, new int[] {-1, 0} };
        
        while (queue.Count > 0 && visited.Count <= maxBlocked) {
            var (x, y) = queue.Dequeue();
            
            if (x == end[0] && y == end[1]) {
                return true;
            }
            
            foreach (var dir in dirs) {
                int nx = x + dir[0], ny = y + dir[1];
                
                if (nx < 0 || nx >= 1000000 || ny < 0 || ny >= 1000000 ||
                    blockSet.Contains((nx, ny)) || visited.Contains((nx, ny))) {
                    continue;
                }
                
                queue.Enqueue((nx, ny));
                visited.Add((nx, ny));
            }
        }
        
        return visited.Count > maxBlocked;
    }
}
var isEscapePossible = function(blocked, source, target) {
    if (blocked.length === 0) return true;
    
    const blockedSet = new Set(blocked.map(b => b[0] + ',' + b[1]));
    const maxArea = blocked.length * (blocked.length - 1) / 2;
    
    const bfs = (start, end) => {
        const visited = new Set();
        const queue = [start];
        visited.add(start[0] + ',' + start[1]);
        
        const dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]];
        
        while (queue.length > 0) {
            const [x, y] = queue.shift();
            
            if (x === end[0] && y === end[1]) return true;
            if (visited.size > maxArea) return true;
            
            for (const [dx, dy] of dirs) {
                const nx = x + dx;
                const ny = y + dy;
                const key = nx + ',' + ny;
                
                if (nx >= 0 && nx < 1000000 && ny >= 0 && ny < 1000000 && 
                    !blockedSet.has(key) && !visited.has(key)) {
                    visited.add(key);
                    queue.push([nx, ny]);
                }
            }
        }
        
        return false;
    };
    
    return bfs(source, target) && bfs(target, source);
};

复杂度分析

复杂度类型分析
时间复杂度O(n²) - 其中 n 是阻塞点数量,最坏情况下需要访问 n(n-1)/2 个格子
空间复杂度O(n²) - 存储阻塞点集合和访问过的格子,最多 O(n²) 个格子