LCP 07-传递信息

Raphael Liu Lv10

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

  1. 有 n 名玩家,所有玩家编号分别为 0 ~ n-1,其中小朋友 A 的编号为 0
  2. 每个玩家都有固定的若干个可传信息的其他玩家(也可能没有)。传信息的关系是单向的(比如 A 可以向 B 传信息,但 B 不能向 A 传信息)。
  3. 每轮信息必须需要传递给另一个人,且信息可重复经过同一个人

给定总玩家数 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)

复杂度分析

  • 时间复杂度:O(n^k)。最多需要遍历 k 层,每层遍历最多有 O(n) 个分支。

  • 空间复杂度:O(n+m+k)。其中 m 为 relation 数组的长度。空间复杂度主要取决于图的大小和递归调用栈的深度,保存有向图信息所需空间为 O(n+m),递归调用栈的深度不会超过 k。

方法二:广度优先搜索

也可以使用广度优先搜索计算方案数。从节点 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

复杂度分析

  • 时间复杂度:O(n^k)。最多需要遍历 k 层,每层遍历最多有 O(n) 个分支。

  • 空间复杂度:O(n+m+n^k)。其中 m 为 relation 数组的长度。空间复杂度主要取决于图的大小和队列的大小,保存有向图信息所需空间为 O(n+m),由于每层遍历最多有 O(n) 个分支,因此遍历到 k 层时,队列的大小为 O(n^k)。

方法三:动态规划

前两种方法都是通过在图中搜索计算方案数。可以换一个思路,这道题是计数问题,可以使用动态规划的方法解决。

定义动态规划的状态 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]

复杂度分析

  • 时间复杂度:O(km)。其中 m 为 relation 数组的长度。

  • 空间复杂度:O(n)。

 Comments
On this page
LCP 07-传递信息