经典图论遍历问题:传递信息

发布时间 2023-03-30 21:18:07作者: 卑以自牧lq

小朋友 A 在和 ta 的小伙伴们玩传信息游戏,游戏规则如下:

有 n 名玩家,所有玩家编号分别为 0 ~ n - 1,其中小朋友 A 的编号为 0
每个玩家都有固定的若干个可传信息的其他玩家(也可能没有)。传信息的关系是单向的(比如 A 可以向 B 传信息,但 B 不能向 A 传信息)。
每轮信息必须需要传递给另一个人,且信息可重复经过同一个人
给定总玩家数 n,以及按 [玩家编号,对应可传递玩家编号] 关系组成的二维数组 relation。返回信息从小 A (编号 0 ) 经过 k 轮传递到编号为 n-1 的小伙伴处的方案数;若不能到达,返回 0。

示例 1:
输入:n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
输出:3
解释:信息从小 A 编号 0 处开始,经 3 轮传递,到达编号 4。共有 3 种方案,分别是 0->2->0->4, 0->2->1->4, 0->2->3->4。

示例 2:
输入:n = 3, relation = [[0,2],[2,1]], k = 2
输出:0
解释:信息不能从小 A 处经过 2 轮传递到编号 2

限制:

  • 2 <= n <= 10
  • 1 <= k <= 5
  • 1 <= relation.length <= 90, 且 relation[i].length == 2
  • 0 <= relation[i][0],relation[i][1] < n 且 relation[i][0] != relation[i][1]

遍历问题可以用两种遍历方式实现

深度优先遍历:从起点开始遍历;
1.遍历当前节点 a
2.如果 a 能到达 b, 则开始遍历 b;对于 b, k = k - 1;
3.如果 b 能到达下一个节点,又开始遍历下一个节点; 对于该节点,k 又 在 b 的基础上减一
...
4.直到 k = 0,或者没有下一个节点时,返回上一个层的节点,继续遍历上一层节点

class Solution {
public:
    int res = 0;
    int target;
    void dfs(vector<vector<int>> &graph, int cur, int k) {
        if (k == 0)
            return;
        for (auto to : graph[cur]) {
            if (to == target && k == 1) {
                res++;
            }
            dfs(graph, to, k - 1);
        }
    }
    int numWays(int n, vector<vector<int>>& relation, int k) {
        vector<vector<int>> graph(n);
        for (const auto &edge : relation) {
            graph[edge[0]].push_back(edge[1]);
        }

        target = n - 1;
        dfs(graph, 0, k);
        return res;
    }
};

广度优先遍历:从起点开始遍历
1.遍历起点能到达的所有节点,加到队列中。队列中保存了第一轮能到达的所有节点
2.遍历队列中的所有节点,对于能到达的节点又加入到队列中,同时把第一轮的节点移除。此时队列中保存了所有第二轮能到达的节点。
...
3.一直遍历 k 轮,此时队列中保存的是第 k 轮能到达的所有节点
4.对这些节点进行判断,判断是否等于 n - 1;

int numWays(int n, vector<vector<int>>& relation, int k) {
    vector<vector<int>> graph(n);
    for (const auto &edge : relation) {
        graph[edge[0]].push_back(edge[1]);
    }

    queue<int>q;
    q.push(0);
    while (k--) {
        int size = q.size();
        for (int i = 0; i < size; i++) {
            int cur = q.front();
            q.pop();
            for (auto to : graph[cur]) {
                q.push(to);
            }
        }
    }
    int res = 0;
    while (!q.empty()) {
        if (q.front() == n - 1) {
            ++res;
        }
        q.pop();
    }
    return res;
}

该问题可以由小问题的结果推导出大问题的结果,也可以用动态规划实现
dp[i][j] 表示经过 i 轮,能到达节点 j 的方案数量。

int numWays(int n, vector<vector<int>>& relation, int k) {
    vector<vector<int>> dp(k + 1, vector<int>(n));
    dp[0][0] = 1;
    for (int i = 1; i <= k; i++) {
        for (const auto &edge : relation) {
            dp[i][edge[1]] += dp[i - 1][edge[0]];
        }
    }
    return dp[k][n - 1];
}