小朋友 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]
方法一:深度优先搜索 可以把传信息的关系看成有向图,每个玩家对应一个节点,每个传信息的关系对应一条有向边。如果 x 可以向 y 传信息,则对应从节点 x 到节点 y 的一条有向 边。寻找从编号 0 的玩家经过 k 轮传递到编号 n-1 的玩家处的方案数,等价于在有向图中寻找从节点 0 到节点 n-1 的长度为 k 的路径数,同一条路径可以重复经过同一个节点。
可以使用深度优先搜索计算方案数。从节点 0 出发做深度优先搜索,每一步记录当前所在的节点以及经过的轮数,当经过 k 轮时,如果位于节点 n-1,则将方案数加 1。搜索结束之后,即可得到总的方案数。
具体实现方面,可以对传信息的关系进行预处理,使用列表存储有向边的关系,即可在 O(1) 的时间内得到特定节点的相邻节点(即可以沿着有向边一步到达的节点)。
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { int ways, n, k; List<List<Integer>> edges; public int numWays (int n, int [][] relation, int k) { ways = 0 ; this .n = n; this .k = k; edges = new ArrayList <List<Integer>>(); for (int i = 0 ; i < n; i++) { edges.add(new ArrayList <Integer>()); } for (int [] edge : relation) { int src = edge[0 ], dst = edge[1 ]; edges.get(src).add(dst); } dfs(0 , 0 ); return ways; } public void dfs (int index, int steps) { if (steps == k) { if (index == n - 1 ) { ways++; } return ; } List<Integer> list = edges.get(index); for (int nextIndex : list) { dfs(nextIndex, steps + 1 ); } } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Solution { int ways, n, k; IList<IList<int >> edges; public int NumWays (int n, int [][] relation, int k ) { ways = 0 ; this .n = n; this .k = k; edges = new List<IList<int >>(); for (int i = 0 ; i < n; i++) { edges.Add(new List<int >()); } foreach (int [] edge in relation) { int src = edge[0 ], dst = edge[1 ]; edges[src].Add(dst); } DFS(0 , 0 ); return ways; } public void DFS (int index, int steps ) { if (steps == k) { if (index == n - 1 ) { ways++; } return ; } IList<int > list = edges[index]; foreach (int nextIndex in list) { DFS(nextIndex, steps + 1 ); } } }
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : int numWays (int n, vector<vector<int >> &relation, int k) { vector<vector<int >> edges (n); for (auto &edge : relation) { int src = edge[0 ], dst = edge[1 ]; edges[src].push_back (dst); } int ways = 0 ; function<void (int , int )> dfs = [&](int index, int steps) { if (steps == k) { if (index == n - 1 ) { ++ways; } return ; } for (int to : edges[index]) { dfs (to, steps + 1 ); } }; dfs (0 , 0 ); return ways; } };
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var numWays = function (n, relation, k ) { let ways = 0 ; const edges = new Array (n).fill (0 ).map (() => new Array ()); for (const [src, dst] of relation) { edges[src].push (dst); } const dfs = (index, steps ) => { if (steps === k) { if (index === n - 1 ) { ways++; } return ; } const list = edges[index]; for (const nextIndex of list) { dfs (nextIndex, steps + 1 ); } } dfs (0 , 0 ); return ways; }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func numWays (n int , relation [][]int , k int ) (ans int ) { edges := make ([][]int , n) for _, r := range relation { src, dst := r[0 ], r[1 ] edges[src] = append (edges[src], dst) } var dfs func (int , int ) dfs = func (x, step int ) { if step == k { if x == n-1 { ans++ } return } for _, y := range edges[x] { dfs(y, step+1 ) } } dfs(0 , 0 ) return }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def numWays (self, n: int , relation: List [int ], k: int ) -> int : self.ways, self.n, self.k = 0 , n, k self.edges = collections.defaultdict(list ) for src, dst in relation: self.edges[src].append(dst) self.dfs(0 ,0 ) return self.ways def dfs (self, index, steps ): if steps == self.k: if index == self.n-1 : self.ways += 1 return for to in self.edges[index]: self.dfs(to, steps+1 )
复杂度分析
方法二:广度优先搜索 也可以使用广度优先搜索计算方案数。从节点 0 出发做广度优先搜索,当遍历到 k 层时,如果位于节点 n-1,则将方案数加 1。搜索结束之后,即可得到总的方案数。
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public int numWays (int n, int [][] relation, int k) { List<List<Integer>> edges = new ArrayList <List<Integer>>(); for (int i = 0 ; i < n; i++) { edges.add(new ArrayList <Integer>()); } for (int [] edge : relation) { int src = edge[0 ], dst = edge[1 ]; edges.get(src).add(dst); } int steps = 0 ; Queue<Integer> queue = new LinkedList <Integer>(); queue.offer(0 ); while (!queue.isEmpty() && steps < k) { steps++; int size = queue.size(); for (int i = 0 ; i < size; i++) { int index = queue.poll(); List<Integer> list = edges.get(index); for (int nextIndex : list) { queue.offer(nextIndex); } } } int ways = 0 ; if (steps == k) { while (!queue.isEmpty()) { if (queue.poll() == n - 1 ) { ways++; } } } return ways; } }
[sol2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Solution { public int NumWays (int n, int [][] relation, int k ) { IList<IList<int >> edges = new List<IList<int >>(); for (int i = 0 ; i < n; i++) { edges.Add(new List<int >()); } foreach (int [] edge in relation) { int src = edge[0 ], dst = edge[1 ]; edges[src].Add(dst); } int steps = 0 ; Queue<int > queue = new Queue<int >(); queue.Enqueue(0 ); while (queue.Count > 0 && steps < k) { steps++; int size = queue.Count; for (int i = 0 ; i < size; i++) { int index = queue.Dequeue(); IList<int > list = edges[index]; foreach (int nextIndex in list) { queue.Enqueue(nextIndex); } } } int ways = 0 ; if (steps == k) { while (queue.Count > 0 ) { if (queue.Dequeue() == n - 1 ) { ways++; } } } return ways; } }
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution {public : int numWays (int n, vector<vector<int >> &relation, int k) { vector<vector<int >> edges (n); for (auto &edge : relation) { int src = edge[0 ], dst = edge[1 ]; edges[src].push_back (dst); } int steps = 0 ; queue<int > que; que.push (0 ); while (!que.empty () && steps < k) { steps++; int size = que.size (); for (int i = 0 ; i < size; i++) { int index = que.front (); que.pop (); for (auto &nextIndex : edges[index]) { que.push (nextIndex); } } } int ways = 0 ; if (steps == k) { while (!que.empty ()) { if (que.front () == n - 1 ) { ways++; } que.pop (); } } return ways; } };
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var numWays = function (n, relation, k ) { const edges = new Array (n).fill (0 ).map (() => new Array ()); for (const [src, dst] of relation) { edges[src].push (dst); } let steps = 0 ; const queue = [0 ]; while (queue.length && steps < k) { steps++; const size = queue.length ; for (let i = 0 ; i < size; i++) { const index = queue.shift (); const list = edges[index]; for (const nextIndex of list) { queue.push (nextIndex); } } } let ways = 0 ; if (steps === k) { while (queue.length ) { if (queue.shift () === n - 1 ) { ways++; } } } return ways; };
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func numWays (n int , relation [][]int , k int ) (ans int ) { edges := make ([][]int , n) for _, r := range relation { src, dst := r[0 ], r[1 ] edges[src] = append (edges[src], dst) } step := 0 q := []int {0 } for ; len (q) > 0 && step < k; step++ { tmp := q q = nil for _, x := range tmp { for _, y := range edges[x] { q = append (q, y) } } } if step == k { for _, x := range q { if x == n-1 { ans++ } } } return }
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution : def numWays (self, n: int , relation: List [int ], k: int ) -> int : edges = collections.defaultdict(list ) for edge in relation: src = edge[0 ] dst = edge[1 ] edges[src].append(dst) steps = 0 queue = collections.deque([0 ]) while queue and steps < k: steps += 1 size = len (queue) for i in range (size): index = queue.popleft() to = edges[index] for nextIndex in to: queue.append(nextIndex) ways = 0 if steps == k: while queue: if queue.popleft() == n - 1 : ways += 1 return ways
复杂度分析
方法三:动态规划 前两种方法都是通过在图中搜索计算方案数。可以换一个思路,这道题是计数问题,可以使用动态规划的方法解决。
定义动态规划的状态 dp}[i][j] 为经过 i 轮传递到编号 j 的玩家的方案数,其中 0 \le i \le k,0 \le j < n。
由于从编号 0 的玩家开始传递,当 i=0 时,一定位于编号 0 的玩家,不会传递到其他玩家,因此动态规划的边界情况如下:
\textit{dp}[0][j]= \begin{cases} 1, & j=0 \ 0, & j \ne 0 \end{cases}
对于传信息的关系 [\textit{src},\textit{dst}],如果第 i 轮传递到编号 src 的玩家,则第 i+1 轮可以从编号 src 的玩家传递到编号 dst 的玩家。因此在计算 dp}[i+1][\textit{dst}] 时,需要考虑可以传递到编号 dst 的所有玩家。由此可以得到动态规划的状态转移方程,其中 0 \le i < k:
\textit{dp}[i+1][\textit{dst}]=\sum_{[\textit{src},\textit{dst}] \in \textit{relation} } \textit{dp}[i][\textit{src}]
最终得到 dp}[k][n-1] 即为总的方案数。
[sol31-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int numWays (int n, int [][] relation, int k) { int [][] dp = new int [k + 1 ][n]; dp[0 ][0 ] = 1 ; for (int i = 0 ; i < k; i++) { for (int [] edge : relation) { int src = edge[0 ], dst = edge[1 ]; dp[i + 1 ][dst] += dp[i][src]; } } return dp[k][n - 1 ]; } }
[sol31-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Solution { public int NumWays (int n, int [][] relation, int k ) { int [,] dp = new int [k + 1 , n]; dp[0 , 0 ] = 1 ; for (int i = 0 ; i < k; i++) { foreach (int [] edge in relation) { int src = edge[0 ], dst = edge[1 ]; dp[i + 1 , dst] += dp[i, src]; } } return dp[k, n - 1 ]; } }
[sol31-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : 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 = 0 ; i < k; i++) { for (auto & edge : relation) { int src = edge[0 ], dst = edge[1 ]; dp[i + 1 ][dst] += dp[i][src]; } } return dp[k][n - 1 ]; } };
[sol31-C] 1 2 3 4 5 6 7 8 9 10 11 12 int numWays (int n, int ** relation, int relationSize, int * relationColSize, int k) { int dp[k + 1 ][n]; memset (dp, 0 , sizeof (dp)); dp[0 ][0 ] = 1 ; for (int i = 0 ; i < k; i++) { for (int j = 0 ; j < relationSize; j++) { int src = relation[j][0 ], dst = relation[j][1 ]; dp[i + 1 ][dst] += dp[i][src]; } } return dp[k][n - 1 ]; }
[sol31-JavaScript] 1 2 3 4 5 6 7 8 9 10 var numWays = function (n, relation, k ) { const dp = new Array (k + 1 ).fill (0 ).map (() => new Array (n).fill (0 )); dp[0 ][0 ] = 1 ; for (let i = 0 ; i < k; i++) { for (const [src, dst] of relation) { dp[i + 1 ][dst] += dp[i][src]; } } return dp[k][n - 1 ]; };
[sol31-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func numWays (n int , relation [][]int , k int ) int { dp := make ([][]int , k+1 ) for i := range dp { dp[i] = make ([]int , n) } dp[0 ][0 ] = 1 for i := 0 ; i < k; i++ { for _, r := range relation { src, dst := r[0 ], r[1 ] dp[i+1 ][dst] += dp[i][src] } } return dp[k][n-1 ] }
[sol31-Python3] 1 2 3 4 5 6 7 8 9 10 class Solution : def numWays (self, n: int , relation: List [List [int ]], k: int ) -> int : dp = [[0 ] * (n + 1 ) for _ in range (k + 1 )] dp[0 ][0 ] = 1 for i in range (k): for edge in relation: src = edge[0 ] dst = edge[1 ] dp[i + 1 ][dst] += dp[i][src] return dp[k][n - 1 ]
上述实现的空间复杂度是 O(kn)。由于当 i>0 时,dp}[i][] 的值只和 dp}[i-1][] 的值有关,因此可以将二维数组变成一维数组,将空间复杂度优化到 O(n)。
[sol32-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int numWays (int n, int [][] relation, int k) { int [] dp = new int [n]; dp[0 ] = 1 ; for (int i = 0 ; i < k; i++) { int [] next = new int [n]; for (int [] edge : relation) { int src = edge[0 ], dst = edge[1 ]; next[dst] += dp[src]; } dp = next; } return dp[n - 1 ]; } }
[sol32-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Solution { public int NumWays (int n, int [][] relation, int k ) { int [] dp = new int [n]; dp[0 ] = 1 ; for (int i = 0 ; i < k; i++) { int [] next = new int [n]; foreach (int [] edge in relation) { int src = edge[0 ], dst = edge[1 ]; next[dst] += dp[src]; } dp = next; } return dp[n - 1 ]; } }
[sol32-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int numWays (int n, vector<vector<int >>& relation, int k) { vector<int > dp (n) ; dp[0 ] = 1 ; for (int i = 0 ; i < k; i++) { vector<int > next (n) ; for (auto & edge : relation) { int src = edge[0 ], dst = edge[1 ]; next[dst] += dp[src]; } dp = next; } return dp[n - 1 ]; } };
[sol32-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int numWays (int n, int ** relation, int relationSize, int * relationColSize, int k) { int dp[n]; memset (dp, 0 , sizeof (dp)); dp[0 ] = 1 ; for (int i = 0 ; i < k; i++) { int next[n]; memset (next, 0 , sizeof (next)); for (int j = 0 ; j < relationSize; j++) { int src = relation[j][0 ], dst = relation[j][1 ]; next[dst] += dp[src]; } memcpy (dp, next, sizeof (int ) * n); } return dp[n - 1 ]; }
[sol32-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 var numWays = function (n, relation, k ) { let dp = new Array (n).fill (0 ); dp[0 ] = 1 ; for (let i = 0 ; i < k; i++) { const next = new Array (n).fill (0 ); for (const [src, dst] of relation) { next[dst] += dp[src]; } dp = next; } return dp[n - 1 ]; };
[sol32-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 func numWays (n int , relation [][]int , k int ) int { dp := make ([]int , n) dp[0 ] = 1 for i := 0 ; i < k; i++ { next := make ([]int , n) for _, r := range relation { src, dst := r[0 ], r[1 ] next[dst] += dp[src] } dp = next } return dp[n-1 ] }
[sol32-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def numWays (self, n: int , relation: List [List [int ]], k: int ) -> int : dp = [0 for _ in range (n + 1 )] dp[0 ] = 1 for i in range (k): next = [0 for _ in range (n + 1 )] for edge in relation: src = edge[0 ] dst = edge[1 ] next [dst] += dp[src] dp = next return dp[n - 1 ]
复杂度分析