关于递归和回溯的思考

完整代码: 力扣112路径总和

class Solution {
private:
    bool traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回

        if (cur->left) { // 左
            count -= cur->left->val; // 递归,处理节点;
            if (traversal(cur->left, count)) return true;
            count += cur->left->val; // 回溯,撤销处理结果
        }
        if (cur->right) { // 右
            count -= cur->right->val; // 递归,处理节点;
            if (traversal(cur->right, count)) return true;
            count += cur->right->val; // 回溯,撤销处理结果
        }
        return false;
    }

public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == NULL) return false;
        return traversal(root, sum - root->val);
    }
};

写法一

if (cur->left) { // 左
            count -= cur->left->val; // 递归,处理节点;
            if (traversal(cur->left, count)) return true;
            count += cur->left->val; // 回溯,撤销处理结果
        }

写法一*

if (cur->left) { // 左
            if (traversal(cur->left, count - cur->left->val)) return true;
        }

写法二

return hasPathSum(root->left, sum - root->val)

两种写法是否等价? 为什么

这两段代码在功能上是等价的,但是它们的实现方式和表达的意图有所不同。

第一段代码是使用了回溯法的思想:

  1. 首先,它检查当前节点的左子节点是否存在。
  2. 如果存在,它先减去左子节点的值,然后递归地调用traversal函数来处理左子节点。
  3. 如果递归调用返回true,则整个函数立即返回true
  4. 如果递归调用返回false,则进行回溯,即撤销之前的操作,将减去的值加回来。

第二段代码是使用了递归的思想:

  • 它直接递归调用hasPathSum函数来处理当前节点的左子节点,同时更新目标和为sum - root->val
  • 这种方式相当于在每一层递归中传递一个更新后的目标和,而不是在同一层中修改和回溯目标和的值。

两种方式都是在尝试找到一条从当前节点到叶子节点的路径,使得路径上节点值的总和等于给定的目标和。在逻辑上,它们是等价的,只是实现方式和表达的思想有所不同。第一种方式更直观地体现了回溯的过程,而第二种方式则更简洁地利用了递归的特性。在实际应用中,可以根据具体情况和个人偏好选择使用哪一种方式。

问题: 为什么第一种方式需要回溯

第一种方式需要回溯是因为它在同一层递归中直接修改了共享的状态(例如目标和 count),而不是传递新的状态给下一层递归。这意味着在当前层递归中对状态的修改会影响到其他递归分支,所以在探索完一个分支后需要撤销这些修改,以便于正确地探索其他分支。这个过程就是回溯。

具体来说,在第一种方式中:

  1. 当进入一个新的递归分支时(例如探索左子树),它会先减去当前节点的值 cur->left->val,更新目标和 count
  2. 然后它递归地探索这个分支。如果这个分支最终满足条件,递归调用会返回 true,整个搜索过程会提前结束。
  3. 如果这个分支不满足条件,递归调用会返回 false,这时需要撤销之前对 count 的修改(即加回 cur->left->val),以便于 count 的值对于探索其他分支(例如右子树)是正确的。

这种需要显式回溯的方式给状态管理带来了额外的复杂性,但它也提供了更大的灵活性,因为它允许你在同一层递归中多次修改和恢复状态。

问题: 为什么第二种方式中不用回溯

第二种方式中不需要回溯,因为它在每次递归调用时都传递了一个新的目标和,而不是直接修改原有的目标和。

在第二种方式中,每一次递归调用 hasPathSum(root->left, sum - root->val) 都是创建了一个新的函数调用栈,这个新的调用栈有自己的局部变量 sum。在这个新的调用栈中,sum 的值是上一层调用栈中的 sum - root->val,这个值在这个新的调用栈中是固定的,不会因为其他递归调用的结果而改变。因此,当这个递归调用返回后,上一层调用栈中的 sum 的值仍然是未修改的,所以不需要进行回溯操作。

换句话说,每一层递归都有自己的变量环境,这些变量环境是相互独立的,所以不需要显式地进行回溯操作来恢复之前的状态。这是递归方法的一个特点,它通过函数调用栈来隐式地实现状态的保存和恢复。

写法一和写法一*完全相同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值