力扣题DFS

给你一个有 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)

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/all-paths-from-source-to-target
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
    //创建一张二维列表
    List<List<Integer>> ret = new LinkedList<>();
    //创建一个栈,用Deque类来实现
    Deque<Integer> stack = new ArrayDeque<>();
    
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        stack.offerLast(0); //这里必须使用offerLast哦,不然结果是反的,可以一试
        dfs(graph, 0, graph.length-1);
        return ret;
    }

    //dfs深度优先搜索遍历
    public void dfs(int[][] graph, int start, int end){
        if(start==end){
            ret.add(new ArrayList<Integer>(stack));//stack是什么,我就用stack去构造一个ArrayList()对象,将其中的每个元素都放到ArrayList里面
            return;
        }

        for(int x : graph[start]){
            stack.offerLast(x);
            dfs(graph, x, end);
            stack.pollLast();
        }
    }
    
}

<think>好的,我需要帮助用户找到LeetCode上整数替换问DFS解法。首先,我得回忆一下这个问的大致内容。整数替换问通常是给定一个正整数n,通过特定的操作将其变为1,求最小的操作次数。可能的操作包括如果n是偶数,则除以2;如果n是奇数,可以选择加1或减1。用户需要的是深度优先搜索(DFS)的解法。 首先,我应该确认目编号。用户提到的是“整数替换”,对应的LeetCode目应该是第397目要求找到将给定整数n转换为1所需的最小替换次数。例如,当n为8时,需要3次操作(8→4→2→1),而n为7时,有两种可能路径:7→8→4→2→1(4步)或者7→6→3→2→1(4步),所以答案是4。 接下来,我需要考虑DFS解法的基本思路。DFS通常通过递归遍历所有可能的路径,选择其中最短的。对于每个奇数,有两种选择:加1或减1。而对于偶数,只有一种选择。因此,递归函数需要处理这两种情况。 但是,直接使用DFS可能会遇到重复计算的问,尤其是当n较大时,会导致栈溢出或时间超限。这时候需要考虑记忆化(Memoization)来优化,存储已经计算过的n的最小步数,避免重复计算。 然后,我需要参考用户提供的引用内容。例如,引用[1]中的组合问使用了DFS,并处理了路径的压入和弹出,这对理解递归回溯有帮助。引用[3]提到了DFS中的Base Case,这里可能是指当n=1时,返回0步,因为已经达到目标。其他引用如子集和括号生成的问,虽然具体问不同,但DFS的结构可能有相似之处,比如递归终止条件和状态传递。 接下来,我需要构建递归函数的框架。函数可能接受当前数值current和已用步数steps作为参数,返回从current到1的最小步数。对于current=1,返回steps。如果current是偶数,只能除以2,递归调用current/2,steps+1。如果是奇数,则有两种选择,加1或减1,分别递归调用,并取两者的最小值加上当前步骤。同时,为了优化,需要使用哈希表或字典来保存已经计算过的current对应的最小步数,避免重复计算。 需要注意,当current为1时,递归终止。另外,对于较大的n,比如2^30,递归深度可能会导致栈溢出,所以可能需要迭代DFS或者使用备忘录来减少递归深度。不过,在LeetCode上,通常合理的优化可以通过记忆化来通过测试用例。 然后,我需要考虑边界情况,比如n=1时,直接返回0;n=2时,1步(2→1)。对于n=3,有两种路径:3→2→1(2步)或3→4→2→1(3步),所以最小值是2。测试这些情况可以验证代码的正确性。 现在,编写代码的大致结构。使用Python的话,可以定义一个带有记忆化的递归函数。例如: memo = {} def dfs(current): if current == 1: return 0 if current in memo: return memo[current] if current % 2 == 0: res = dfs(current // 2) + 1 else: res = min(dfs(current + 1), dfs(current - 1)) + 1 memo[current] = res return res 然后返回dfs(n)的结果。不过,对于current+1的情况,当current是较大的奇数时,比如2^31-1,加1会导致溢出,但在Python中整数大小不受限制,所以这可能不是问。不过,在LeetCode的测试用例中,可能需要处理这种情况,但通常目会保证输入的合法性。 不过,这样的递归在遇到大数时,比如n=1000000,会导致大量的递归调用,可能会超时。因此,需要进一步优化。例如,当current是奇数时,current+1或current-1中的一个会是偶数,而偶数的情况会立即被除以2,所以可以进一步优化递归步骤。例如,对于奇数current,比较dfs((current+1)//2)和dfs((current-1)//2),然后取较小的那个,再加2步(因为current→current±1→(current±1)/2)。这样可以减少递归的深度。 例如,对于奇数current,计算min(dfs((current + 1) // 2) + 2, dfs((current - 1) // 2) + 2)。这样可以将两步合并,减少递归次数。这是因为current±1后变成偶数,下一步必然除以2,所以两步可以合并为一个操作。例如,current是7的话,7→8→4是两步,或者7→6→3也是两步。这样处理可以减少递归调用的次数,提高效率。 因此,优化后的递归函数可能如下: memo = {} def dfs(current): if current == 1: return 0 if current in memo: return memo[current] if current % 2 == 0: res = dfs(current // 2) + 1 else: res = min(dfs((current + 1) // 2), dfs((current - 1) // 2)) + 2 memo[current] = res return res 这种优化可以显著减少递归的次数,特别是对于较大的current值。例如,当current是较大的奇数时,避免了两次递归调用,而是直接处理到下一步的偶数情况。 再测试一下n=3的情况:3是奇数,进入else分支,(3+1)/2=2和(3-1)/2=1。计算dfs(2)和dfs(1)。dfs(2)返回1(2→1),dfs(1)返回0。所以min(1,0)=0,加上2,得到2,正确。 另一个测试案例n=7:进入else分支,(7+1)/2=4和(7-1)/2=3。dfs(4)需要2步(4→2→1),dfs(3)需要2步(如上述)。所以min(2,2)=2,加上2,得到4,正确。 这样优化后,应该能处理更大的数值而不会超时。同时,使用记忆化存储中间结果,避免重复计算。 总结,DFS解法的关键在于递归处理每个步骤,并通过记忆化优化重复子问。对于奇数情况,合并两步操作可以减少递归深度,提高效率。需要注意递归终止条件和边界情况的处理,如n=1时返回0。</think>### LeetCode 397. 整数替换(DFS解法) **问描述** 给定一个正整数 $n$,通过以下操作将其变为 1,求最小操作次数: - 若 $n$ 是偶数,则用 $n/2$ 替换 $n$ - 若 $n$ 是奇数,则可用 $n+1$ 或 $n-1$ 替换 $n$ --- ### DFS解法思路 1. **递归终止条件** - 当 $n=1$ 时,返回操作次数 0。 2. **递归逻辑** - 若 $n$ 是偶数:直接递归处理 $n/2$,操作次数加 1。 - 若 $n$ 是奇数:分别递归处理 $n+1$ 和 $n-1$,选择更小的操作次数,并额外加 2 次操作(因为奇数的操作需要两步:$n→n±1→(n±1)/2$)。 3. **记忆化优化** - 使用哈希表存储中间结果,避免重复计算。 --- ### Python代码实现 ```python class Solution: def integerReplacement(self, n: int) -> int: memo = {} def dfs(current): if current == 1: return 0 if current in memo: return memo[current] if current % 2 == 0: res = dfs(current // 2) + 1 else: # 合并两步操作:n → n±1 → (n±1)/2,共两步 res = min(dfs((current + 1) // 2), dfs((current - 1) // 2)) + 2 memo[current] = res return res return dfs(n) ``` --- ### 关键点分析 1. **时间复杂度优化** - 记忆化避免重复计算子问,时间复杂度从指数级降为 $O(\log n)$。 2. **奇数的合并操作** - 将奇数的两次操作(加 1/减 1,再除以 2)合并为一步,减少递归深度[^3]。 3. **边界条件处理** - 当 $n=2^{31}-1$ 时,直接递归可能导致栈溢出,但合并操作和记忆化优化能有效规避。 --- ### 示例说明 - **输入**:$n=7$ **路径**:$7→8→4→2→1$ 或 $7→6→3→2→1$ **输出**:4 - **输入**:$n=8$ **路径**:$8→4→2→1$ **输出**:3 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值