Hard

题目描述

给你一个整数 n 表示图中的节点数,节点标记从 0 到 n - 1。

同时给你一个长度为 n 的整数数组 nums 和一个整数 maxDiff。

如果 nums[i] 和 nums[j] 的绝对差值不超过 maxDiff(即 |nums[i] - nums[j]| <= maxDiff),则节点 i 和 j 之间存在一条无向边。

还给你一个二维整数数组 queries。对于每个 queries[i] = [ui, vi],求节点 ui 和 vi 之间的最短距离。如果两个节点之间不存在路径,则该查询返回 -1。

返回数组 answer,其中 answer[i] 是第 i 个查询的结果。

注意:节点之间的边都是无权重的。

示例 1:

输入:n = 5, nums = [1,8,3,4,2], maxDiff = 3, queries = [[0,3],[2,4]]
输出:[1,1]
解释:
生成的图为:

查询 [0, 3]:最短路径 0 → 3,距离为 1
查询 [2, 4]:最短路径 2 → 4,距离为 1

因此输出为 [1, 1]。

示例 2:

输入:n = 5, nums = [5,3,1,9,10], maxDiff = 2, queries = [[0,1],[0,2],[2,3],[4,3]]
输出:[1,2,-1,1]

示例 3:

输入:n = 3, nums = [3,6,1], maxDiff = 1, queries = [[0,0],[1,2]]
输出:[0,-1]

提示:

  • 1 <= n == nums.length <= 10^5
  • 0 <= nums[i] <= 10^5
  • 0 <= maxDiff <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i] == [ui, vi]
  • 0 <= ui, vi < n

解题思路

这道题的核心思路是构建图并高效计算最短路径。

暴力做法:对每个查询都进行BFS,时间复杂度为 O(Q×(N+E)),其中Q是查询数,在最坏情况下会超时。

优化思路:利用稀疏表(Sparse Table)+ 二进制跳跃的思想。

  1. 排序优化:将节点按照 nums[i] 排序。排序后相邻的节点更容易满足 maxDiff 条件,这样可以更高效地构建图。

  2. 稀疏表预处理:对于每个节点,预处理它能到达的距离为 2^k 的所有节点。使用动态规划思想:

    • dp[i][k] 表示从节点 i 出发,走 2^k 步能到达的所有节点
    • dp[i][0] 表示从 i 直接相邻的节点
    • dp[i][k] = dp[i][k-1] ∪ dp[dp[i][k-1]][k-1]
  3. 查询处理:对于每个查询 (u,v),使用二进制分解来寻找最短路径:

    • 从小到大枚举距离 d = 1,2,3,…
    • 使用二进制表示,检查是否能从 u 以距离 d 到达 v
    • 一旦找到,立即返回距离 d

这种方法的时间复杂度为 O(N log N + Q × N log N),空间复杂度为 O(N^2 log N)。

实现要点

  • 使用邻接表存储图结构
  • 稀疏表使用 vector<set> 存储每层可达节点
  • 查询时进行二进制枚举距离

代码实现

class Solution {
public:
    vector<int> pathExistenceQueries(int n, vector<int>& nums, int maxDiff, vector<vector<int>>& queries) {
        vector<pair<int, int>> sorted_nodes;
        for (int i = 0; i < n; i++) {
            sorted_nodes.push_back({nums[i], i});
        }
        sort(sorted_nodes.begin(), sorted_nodes.end());
        
        vector<vector<int>> graph(n);
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (sorted_nodes[j].first - sorted_nodes[i].first > maxDiff) break;
                int u = sorted_nodes[i].second, v = sorted_nodes[j].second;
                graph[u].push_back(v);
                graph[v].push_back(u);
            }
        }
        
        int LOG = 17;
        vector<vector<set<int>>> dp(n, vector<set<int>>(LOG));
        
        for (int i = 0; i < n; i++) {
            for (int j : graph[i]) {
                dp[i][0].insert(j);
            }
        }
        
        for (int k = 1; k < LOG; k++) {
            for (int i = 0; i < n; i++) {
                for (int mid : dp[i][k-1]) {
                    for (int j : dp[mid][k-1]) {
                        dp[i][k].insert(j);
                    }
                }
            }
        }
        
        vector<int> result;
        for (auto& query : queries) {
            int u = query[0], v = query[1];
            if (u == v) {
                result.push_back(0);
                continue;
            }
            
            int ans = -1;
            for (int dist = 1; dist < n && ans == -1; dist++) {
                set<int> reachable;
                reachable.insert(u);
                
                for (int k = 0; k < LOG; k++) {
                    if (dist & (1 << k)) {
                        set<int> next_reachable;
                        for (int node : reachable) {
                            for (int next : dp[node][k]) {
                                next_reachable.insert(next);
                            }
                        }
                        reachable = next_reachable;
                    }
                }
                
                if (reachable.count(v)) {
                    ans = dist;
                }
            }
            result.push_back(ans);
        }
        
        return result;
    }
};
class Solution:
    def pathExistenceQueries(self, n: int, nums: List[int], maxDiff: int, queries: List[List[int]]) -> List[int]:
        sorted_nodes = sorted((nums[i], i) for i in range(n))
        
        graph = [[] for _ in range(n)]
        for i in range(n):
            for j in range(i + 1, n):
                if sorted_nodes[j][0] - sorted_nodes[i][0] > maxDiff:
                    break
                u, v = sorted_nodes[i][1], sorted_nodes[j][1]
                graph[u].append(v)
                graph[v].append(u)
        
        LOG = 17
        dp = [[set() for _ in range(LOG)] for _ in range(n)]
        
        for i in range(n):
            for j in graph[i]:
                dp[i][0].add(j)
        
        for k in range(1, LOG):
            for i in range(n):
                for mid in dp[i][k-1]:
                    dp[i][k].update(dp[mid][k-1])
        
        result = []
        for u, v in queries:
            if u == v:
                result.append(0)
                continue
            
            ans = -1
            for dist in range(1, n):
                reachable = {u}
                
                for k in range(LOG):
                    if dist & (1 << k):
                        next_reachable = set()
                        for node in reachable:
                            next_reachable.update(dp[node][k])
                        reachable = next_reachable
                
                if v in reachable:
                    ans = dist
                    break
            
            result.append(ans)
        
        return result
public class Solution {
    public int[] PathExistenceQueries(int n, int[] nums, int maxDiff, int[][] queries) {
        var sortedNodes = nums.Select((val, idx) => new { Val = val, Idx = idx })
                              .OrderBy(x => x.Val)
                              .ToArray();
        
        var graph = new List<int>[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new List<int>();
        }
        
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (sortedNodes[j].Val - sortedNodes[i].Val > maxDiff) break;
                int u = sortedNodes[i].Idx, v = sortedNodes[j].Idx;
                graph[u].Add(v);
                graph[v].Add(u);
            }
        }
        
        int LOG = 17;
        var dp = new HashSet<int>[n][];
        for (int i = 0; i < n; i++) {
            dp[i] = new HashSet<int>[LOG];
            for (int j = 0; j < LOG; j++) {
                dp[i][j] = new HashSet<int>();
            }
        }
        
        for (int i = 0; i < n; i++) {
            foreach (int j in graph[i]) {
                dp[i][0].Add(j);
            }
        }
        
        for (int k = 1; k < LOG; k++) {
            for (int i = 0; i < n; i++) {
                foreach (int mid in dp[i][k-1]) {
                    foreach (int j in dp[mid][k-1]) {
                        dp[i][k].Add(j);
                    }
                }
            }
        }
        
        var result = new List<int>();
        foreach (var query in queries) {
            int u = query[0], v = query[1];
            if (u == v) {
                result.Add(0);
                continue;
            }
            
            int ans = -1;
            for (int dist = 1; dist < n && ans == -1; dist++) {
                var reachable = new HashSet<int> { u };
                
                for (int k = 0; k < LOG; k++) {
                    if ((dist & (1 << k)) != 0) {
                        var nextReachable = new HashSet<int>();
                        foreach (int node in reachable) {
                            foreach (int next in dp[node][k]) {
                                nextReachable.Add(next);
                            }
                        }
                        reachable = nextReachable;
                    }
                }
                
                if (reachable.Contains(v)) {
                    ans = dist;
                }
            }
            result.Add(ans);
        }
        
        return result.ToArray();
    }
}
var pathExistenceQueries = function(n, nums, maxDiff, queries) {
    const adj = Array.from({length: n}, () => []);
    
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            if (Math.abs(nums[i] - nums[j]) <= maxDiff) {
                adj[i].push(j);
                adj[j].push(i);
            }
        }
    }
    
    function bfs(start, end) {
        if (start === end) return 0;
        
        const queue = [start];
        const visited = new Set([start]);
        let distance = 0;
        
        while (queue.length > 0) {
            const size = queue.length;
            distance++;
            
            for (let i = 0; i < size; i++) {
                const node = queue.shift();
                
                for (const neighbor of adj[node]) {
                    if (neighbor === end) return distance;
                    if (!visited.has(neighbor)) {
                        visited.add(neighbor);
                        queue.push(neighbor);
                    }
                }
            }
        }
        
        return -1;
    }
    
    return queries.map(([u, v]) => bfs(u, v));
};

复杂度分析

复杂度类型时间复杂度空间复杂度
预处理O(N² + N² log N)O(N² log N)
单次查询O(N log N)O(N)
总体O(N² log N + Q × N log N)O(N² log N)

说明

  • 预处理阶段:构建图需要 O(N²),构建稀疏表需要 O(N² log N)
  • 查询阶段:每次查询最多遍历 N 个距离,每个距离的计算需要 O(N log N)
  • 空间主要消耗在稀疏表上,存储每个节点在不同跳数下的可达节点集合