Hard

题目描述

给你一个整数 n,表示网络中的人数。每个人都有一个从 0n - 1 的标签。

同时给你一个下标从 0 开始的二维整数数组 restrictions,其中 restrictions[i] = [xi, yi] 意味着 xiyi 不能成为朋友,无论是直接还是通过其他人间接成为朋友。

初始时,没有人是朋友。给你一个朋友请求列表,用一个下标从 0 开始的二维整数数组 requests 表示,其中 requests[j] = [uj, vj]ujvj 之间的朋友请求。

ujvj 可以成为朋友时,朋友请求就成功了。每个朋友请求都按给定的顺序处理(即,requests[j]requests[j + 1] 之前出现),并且对于成功的请求,ujvj 会在以后的所有朋友请求中成为直接朋友。

返回一个布尔数组 result,其中 result[j]true,如果第 j 个朋友请求是成功的,否则为 false

注意:如果 ujvj 已经是直接朋友,请求仍然成功。

示例 1:

输入:n = 3, restrictions = [[0,1]], requests = [[0,2],[2,1]]
输出:[true,false]
解释:
请求 0:人员 0 和人员 2 可以成为朋友,所以他们成为直接朋友。
请求 1:人员 2 和人员 1 不能成为朋友,因为人员 0 和人员 1 会成为间接朋友 (1--2--0)。

示例 2:

输入:n = 3, restrictions = [[0,1]], requests = [[1,2],[0,2]]
输出:[true,false]
解释:
请求 0:人员 1 和人员 2 可以成为朋友,所以他们成为直接朋友。
请求 1:人员 0 和人员 2 不能成为朋友,因为人员 0 和人员 1 会成为间接朋友 (0--2--1)。

示例 3:

输入:n = 5, restrictions = [[0,1],[1,2],[2,3]], requests = [[0,4],[1,2],[3,1],[3,4]]
输出:[true,false,true,false]
解释:
请求 0:人员 0 和人员 4 可以成为朋友,所以他们成为直接朋友。
请求 1:人员 1 和人员 2 不能成为朋友,因为他们直接受到限制。
请求 2:人员 3 和人员 1 可以成为朋友,所以他们成为直接朋友。
请求 3:人员 3 和人员 4 不能成为朋友,因为人员 0 和人员 1 会成为间接朋友 (0--4--3--1)。

提示:

  • 2 <= n <= 1000
  • 0 <= restrictions.length <= 1000
  • restrictions[i].length == 2
  • 0 <= xi, yi <= n - 1
  • xi != yi
  • 1 <= requests.length <= 1000
  • requests[j].length == 2
  • 0 <= uj, vj <= n - 1
  • uj != vj

解题思路

这道题的核心思路是使用并查集来维护朋友关系,同时在每次合并操作前检查是否会违反限制条件。

基本思路:

  1. 并查集维护朋友关系:使用并查集来表示朋友网络,同一个连通分量中的所有人都是间接朋友关系。

  2. 检查限制条件:对于每个朋友请求 [u, v],我们需要检查合并 uv 所在的连通分量后,是否会让某个限制对 [x, y] 中的 xy 处于同一个连通分量。

  3. 模拟合并过程:为了避免实际修改并查集状态,我们可以创建一个临时的并查集副本,在其上模拟合并操作,检查是否违反限制。

算法步骤:

  1. 初始化并查集,每个人最初都是独立的连通分量
  2. 对于每个请求 [u, v]
    • 如果 uv 已经在同一个连通分量,直接返回 true
    • 否则创建并查集的副本,模拟合并 uv
    • 检查所有限制对,看是否有受限的两人会在合并后处于同一连通分量
    • 如果没有违反限制,则执行真正的合并操作,记录结果为 true
    • 如果违反限制,记录结果为 false

这种方法的时间复杂度相对较高但思路清晰,适合题目给定的数据规模。

代码实现

class Solution {
public:
    vector<int> parent;
    
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    
    void unite(int x, int y) {
        int px = find(x), py = find(y);
        if (px != py) {
            parent[px] = py;
        }
    }
    
    bool connected(int x, int y) {
        return find(x) == find(y);
    }
    
    vector<bool> friendRequests(int n, vector<vector<int>>& restrictions, vector<vector<int>>& requests) {
        parent.resize(n);
        iota(parent.begin(), parent.end(), 0);
        
        vector<bool> result;
        
        for (auto& req : requests) {
            int u = req[0], v = req[1];
            
            // 如果已经是朋友,直接成功
            if (connected(u, v)) {
                result.push_back(true);
                continue;
            }
            
            // 创建临时并查集来模拟合并
            vector<int> tempParent = parent;
            parent = tempParent;
            unite(u, v);
            
            // 检查是否违反限制
            bool valid = true;
            for (auto& restriction : restrictions) {
                int x = restriction[0], y = restriction[1];
                if (connected(x, y)) {
                    valid = false;
                    break;
                }
            }
            
            if (valid) {
                result.push_back(true);
                // 保持合并后的状态
            } else {
                result.push_back(false);
                // 恢复原状态
                parent = tempParent;
            }
        }
        
        return result;
    }
};
class Solution:
    def friendRequests(self, n: int, restrictions: List[List[int]], requests: List[List[int]]) -> List[bool]:
        parent = list(range(n))
        
        def find(x):
            if parent[x] != x:
                parent[x] = find(parent[x])
            return parent[x]
        
        def unite(x, y):
            px, py = find(x), find(y)
            if px != py:
                parent[px] = py
        
        def connected(x, y):
            return find(x) == find(y)
        
        result = []
        
        for u, v in requests:
            # 如果已经是朋友,直接成功
            if connected(u, v):
                result.append(True)
                continue
            
            # 创建临时并查集来模拟合并
            temp_parent = parent[:]
            unite(u, v)
            
            # 检查是否违反限制
            valid = True
            for x, y in restrictions:
                if connected(x, y):
                    valid = False
                    break
            
            if valid:
                result.append(True)
                # 保持合并后的状态
            else:
                result.append(False)
                # 恢复原状态
                parent = temp_parent
        
        return result
public class Solution {
    private int[] parent;
    
    private int Find(int x) {
        if (parent[x] != x) {
            parent[x] = Find(parent[x]);
        }
        return parent[x];
    }
    
    private void Unite(int x, int y) {
        int px = Find(x), py = Find(y);
        if (px != py) {
            parent[px] = py;
        }
    }
    
    private bool Connected(int x, int y) {
        return Find(x) == Find(y);
    }
    
    public bool[] FriendRequests(int n, int[][] restrictions, int[][] requests) {
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        
        List<bool> result = new List<bool>();
        
        foreach (var req in requests) {
            int u = req[0], v = req[1];
            
            // 如果已经是朋友,直接成功
            if (Connected(u, v)) {
                result.Add(true);
                continue;
            }
            
            // 创建临时并查集来模拟合并
            int[] tempParent = new int[n];
            Array.Copy(parent, tempParent, n);
            Unite(u, v);
            
            // 检查是否违反限制
            bool valid = true;
            foreach (var restriction in restrictions) {
                int x = restriction[0], y = restriction[1];
                if (Connected(x, y)) {
                    valid = false;
                    break;
                }
            }
            
            if (valid) {
                result.Add(true);
                // 保持合并后的状态
            } else {
                result.Add(false);
                // 恢复原状态
                parent = tempParent;
            }
        }
        
        return result.ToArray();
    }
}
var friendRequests = function(n, restrictions, requests) {
    class UnionFind {
        constructor(n) {
            this.parent = Array.from({length: n}, (_, i) => i);
            this.rank = new Array(n).fill(0);
        }
        
        find(x) {
            if (this.parent[x] !== x) {
                this.parent[x] = this.find(this.parent[x]);
            }
            return this.parent[x];
        }
        
        union(x, y) {
            let rootX = this.find(x);
            let rootY = this.find(y);
            
            if (rootX === rootY) return;
            
            if (this.rank[rootX] < this.rank[rootY]) {
                this.parent[rootX] = rootY;
            } else if (this.rank[rootX] > this.rank[rootY]) {
                this.parent[rootY] = rootX;
            } else {
                this.parent[rootY] = rootX;
                this.rank[rootX]++;
            }
        }
        
        connected(x, y) {
            return this.find(x) === this.find(y);
        }
        
        copy() {
            let newUF = new UnionFind(this.parent.length);
            newUF.parent = [...this.parent];
            newUF.rank = [...this.rank];
            return newUF;
        }
    }
    
    let uf = new UnionFind(n);
    let result = [];
    
    for (let [u, v] of requests) {
        let tempUF = uf.copy();
        tempUF.union(u, v);
        
        let valid = true;
        for (let [x, y] of restrictions) {
            if (tempUF.connected(x, y)) {
                valid = false;
                break;
            }
        }
        
        if (valid) {
            uf.union(u, v);
            result.push(true);
        } else {
            result.push(false);
        }
    }
    
    return result;
};

复杂度分析

复杂度类型分析
时间复杂度O(requests.length × restrictions.length × α(n)),其中 α(n) 是反阿克曼函数,对于每个请求需要检查所有限制条件
空间复杂度O(n),并查集需要 O(n) 空间,临时数组也需要 O(n) 空间

相关题目