深度优先搜索解所有可能的路径问题
题目描述
力扣链接
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
示例 1:
输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
示例 2:
输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
提示:
n == graph.length
2 <= n <= 15
0 <= graph[i][j] < n
graph[i][j] != i(即不存在自环)
graph[i] 中的所有元素 互不相同
保证输入为 有向无环图(DAG)
解题思路
由于是有向无环图,可以使用深度优先搜索(DFS)遍历所有可能路径。通过回溯法维护当前路径,在到达终点时保存路径到结果集[[2]][[5]]。
关键实现步骤
- 维护两个数据结构:
ans
:存储所有合法路径path
:记录当前遍历路径(栈结构)
- 从节点0开始DFS
- 遍历当前节点的所有邻居:
- 将邻居节点加入路径
- 递归处理邻居节点
- 回溯时弹出最后加入的节点
- 当到达终点n-1时,将当前路径加入结果集
代码实现
Java版本
class Solution {
//最终返回结果
List<List<Integer>> ans = new ArrayList();
//保存其中的一条结果
Deque<Integer> stack = new ArrayDeque();
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
//graph[i] 是一个从节点 i 可以访问的所有节点的列表
//因为从节点0开始
stack.offerLast(0);
//调用
dfs(graph,0,graph.length-1);
return ans;
}
public void dfs(int[][] graph,int x,int n){
//x表示当前遍历的节点 n表示要到达的节点
if(x == n){
//如果x == n 说明遍历到了最后节点 将stack作为一个结果存入
ans.add(new ArrayList<Integer>(stack));
return;
}
//graph[x] 表示当前节点x的所有邻居节点
for(int y:graph[x]){
//遍历节点x的每个邻居节点
//1、将y存入
stack.offerLast(y);
//递归
dfs(graph,y,n);
//回溯
stack.pollLast();
}
}
}
JavaScript版本
var allPathsSourceTarget = function(graph) {
const ans = [];
const dfs = (curr, path) => {
if (curr === graph.length - 1) {
ans.push([...path]);
return;
}
for (const neighbor of graph[curr]) {
path.push(neighbor);
dfs(neighbor, path);
path.pop();
}
};
dfs(0, [0]);
return ans;
};
C++版本
class Solution {
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> ans;
vector<int> path = {0};
dfs(graph, 0, graph.size()-1, path, ans);
return ans;
}
private:
void dfs(vector<vector<int>>& graph, int curr, int target,
vector<int>& path, vector<vector<int>>& ans) {
if (curr == target) {
ans.push_back(path);
return;
}
for (int neighbor : graph[curr]) {
path.push_back(neighbor);
dfs(graph, neighbor, target, path, ans);
path.pop_back();
}
}
};
Go版本
func allPathsSourceTarget(graph [][]int) [][]int {
var ans [][]int
var path = []int{0}
var dfs func(int)
dfs = func(curr int) {
if curr == len(graph)-1 {
ans = append(ans, append([]int{}, path...))
return
}
for _, neighbor := range graph[curr] {
path = append(path, neighbor)
dfs(neighbor)
path = path[:len(path)-1]
}
}
dfs(0)
return ans
}
Python版本
class Solution:
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
ans = []
path = [0]
n = len(graph)
def dfs(curr):
if curr == n - 1:
ans.append(list(path))
return
for neighbor in graph[curr]:
path.append(neighbor)
dfs(neighbor)
path.pop()
dfs(0)
return ans
复杂度分析
- 时间复杂度:O(2^N * N),每个节点最多有2种选择(选或不选),路径最长为N
- 空间复杂度:O(N),递归栈深度和路径存储空间[[6]][[8]]
总结
该问题展示了DFS在图遍历中的典型应用。通过维护路径栈实现回溯,可以高效地遍历所有可能路径。不同语言实现时需要注意:
- Go/Python需要显式复制路径切片
- JavaScript通过闭包简化参数传递
- C++使用vector引用传递提升效率[[4]][[9]]
参考:[[1]][[2]][[5]][[7]]