Go 递归与回溯:为什么 int 参数不需要手动"恢复"?
这是一个非常好的问题,触及了递归和回溯的核心概念。
简短回答:不需要手动恢复,你的代码是完全正确的。
为什么不需要恢复?这涉及到编程语言中参数传递的机制。
1. 值传递机制
在 Go 语言中,int 类型是基本类型,函数参数是值传递的。
当你在函数内部执行 targetSum -= root.Val 时,你修改的只是当前函数栈帧中 targetSum 这个局部变量的副本。
当你调用下一层递归 hasPathSum(root.Left, targetSum) 时:
- Go 会把当前的
targetSum值复制一份传给新的函数 - 新的函数拿到的是一个全新的副本,它的修改不会影响到上一层函数里的
targetSum
举个例子,假设当前节点值是 5,targetSum 是 22:
- 进入函数,
targetSum是 22 - 执行
targetSum -= 5,当前层级的targetSum变成了 17 - 调用左子树:
hasPathSum(left, 17)。左子树函数内部怎么折腾这个 17,都不会改变当前层级的 17,更不会变回 22 - 关键点:当左子树调用结束,程序回到当前层级,准备执行右子树
hasPathSum(right, targetSum)时,这里的targetSum依然是刚才算出来的 17
所以,对于这道题,这种写法天然就是”正确”的——每一层都拿着自己的一份”账单副本”在计算。
2. 什么时候需要”恢复”?
你可能在其他的回溯算法(如全排列、组合总和)中见过需要”恢复状态”的代码,通常长这样:
1 | // 典型的回溯模板 |
为什么这里需要恢复,而上面的 targetSum 不需要?
区别在于数据结构:
targetSum(int):值类型。函数调用时自动复制,天然的隔离保护了每一层的数据,所以不需要手动恢复path(切片/slice):引用类型(或者理解为它传递的是内存地址)。所有递归层级共享同一个path数组。如果递归左子树时往里面加了东西,递归完不拿出来,遍历右子树时path里就会带着左子树的数据,导致结果错误
3. 如果是指针传递呢?
如果你的函数参数不是 int,而是一个指针(比如 *int),那么你就需要恢复:
1 | func hasPathSum(root *TreeNode, targetSum *int) bool { |
总结
代码逻辑图解如下:
1 | 进入节点 A (targetSum = 22) |
因为 targetSum 是通过值传递的,每一层递归都有自己独立的变量副本,互不干扰,所以不需要也不存在”恢复”一说。你的写法是标准的 DFS 写法。
核心结论:值类型天然隔离,引用类型需要手动恢复。 判断是否需要”撤销选择”,看参数是值传递还是引用传递即可。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 DeepThinking深思!
评论
