11.29自底向上 DFS

104. 二叉树的最大深度

思路:自顶向上的方法

因为回溯和深搜是绑定的,回溯的过程涉及到depth的增减,所以DFS要把depth作为参数传递下去,而自底向上不用。

class Solution {
public:
    int maxDepth(TreeNode* root , int depth = 1) {//这里有参数depth其实是自顶向下的做法
      if(!root)  return 0;     
      if(root->left == root->right)  return depth;
      return max(maxDepth(root->left , depth + 1),maxDepth(root->right , depth + 1));
    }
};

灵神的做法:不必多加参数depth传递下去,并不是要找特定的某一层depth == k

class Solution {
public:
    int maxDepth(TreeNode* root ) {
      if(!root)  return 0;     
      
      return max(maxDepth(root->left),maxDepth(root->right)) + 1;
    }
};

自顶向下:

class Solution {
    int res = 0;
    int dfs(TreeNode* node, int depth){
      if(!node) return depth;    
      return max(dfs(node->left , depth + 1),dfs(node->right , depth + 1));     
    }

public:
    int maxDepth(TreeNode* root) {
        
      return dfs(root , 0);//这里root的depth一定是0而不是1
    }
};

灵神版:

auto& dfs 允许你在 lambda 表达式内部递归地调用 dfs,而不需要额外的参数。这是解决递归问题时常用的技巧,特别是在需要在递归过程中保持某些状态或避免重复定义时。

通过引用捕获自身,允许在 lambda 内部递归调用。这种方式简洁且高效,避免了显式的参数传递和对象复制。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        int ans = 0;
        auto dfs = [&](auto& dfs, TreeNode* node, int depth) -> void {
            if (node == nullptr) {
                return;
            }
            depth++;//dfs这里按值传递参数不需要手动回溯
            ans = max(ans, depth);
            dfs(dfs, node->left, depth);
            dfs(dfs, node->right, depth);
          //这里不需要再加depth --
        };
        dfs(dfs, root, 0);
        return ans;
    }
};

是否需要手动回溯

需要手动回溯的情况:

  1. 路径问题:在解决路径问题,如寻找所有路径、路径计数或路径上的特定条件时,通常需要在递归返回前手动撤销一些操作,以确保下一次递归调用能够从正确的状态开始。

  2. 状态修改:如果你在递归过程中修改了对象的状态(如数组元素、节点的标记等),而这些修改不是最终答案的一部分,那么在递归返回前需要将状态恢复到修改前的状态。

  3. 动态规划:在动态规划问题中,如果你使用递归树来避免重复计算,那么在递归调用结束后,可能需要恢复一些状态,以确保不影响其他分支的计算。

  4. 图的遍历:在图的遍历中,为了避免重复访问节点,通常需要在访问后手动标记节点,然后在递归返回前取消标记。

不需要手动回溯的情况:

  1. 树的深度优先搜索(DFS):在树的深度优先搜索中,通常不需要手动回溯,因为每次递归调用都是独立的,不会影响其他调用的状态。

  2. 分治算法:在分治算法中,子问题的解通常是独立的,不需要在递归返回后恢复任何状态。

  3. 简单的递归:对于简单的递归问题,如计算阶乘、斐波那契数列等,不需要手动回溯,因为每次递归调用都是自包含的,不依赖于外部状态。

  4. 按值传递参数:如果你的递归函数通过值传递参数,那么每次递归调用都会有参数的副本,不需要手动回溯,因为递归结束后,局部变量的生命周期就结束了。

  5. 函数返回值:如果递归函数的目的是计算并返回一个值,而不是修改外部状态,那么通常不需要手动回溯。

总结来说,是否需要手动回溯取决于你的算法是否依赖于外部状态的修改,以及这些状态是否需要在递归调用之间保持一致。如果递归过程中的修改是局部的,不影响其他递归调用,那么就不需要手动回溯。如果递归过程中的修改需要在递归调用之间共享或保持一致,那么可能需要手动回溯以确保状态的正确性。


111. 二叉树的最小深度

思路:注意和求最大深度相区别开

以下是错误代码:

class Solution{
public:
    int minDepth(TreeNode* root) {
        if(!root)  return 0;
        int l_min = minDepth(root->left);
        int r_min = minDepth(root->right);
        return min(l_min , r_min) + 1;       
    }
};

在求最小深度时,我们需要考虑树可能不是完全二叉树,可能只有一个左子树或一个右子树。在这种情况下,我们需要返回非空子树的深度加1,如样例{1 , 2},最小高度为2而不是1.

这种做法看似把问题拆分为求左子树最小高度、右子树最小高度,但是返回时不对。只有左右子树均存在的时候可以这样求。应该对叶子节点进行判断,考虑只有左/右子树的情况。

class Solution {
public:
    int minDepth(TreeNode* root) {
        if (root == nullptr) {
            return 0; // 空树的最小深度为0
        }
        if (root->left == nullptr) {
            return minDepth(root->right) + 1; // 只有右子树
        }
        if (root->right == nullptr) {
            return minDepth(root->left) + 1; // 只有左子树
        }
        return min(minDepth(root->left), minDepth(root->right)) + 1; // 两个子树都存在
    }
};

自顶向下:

class Solution{
public:
    int minDepth(TreeNode* root) {
        if(!root)  return 0;
        int res = INT_MAX;
        auto dfs = [&](auto& dfs ,TreeNode* node, int depth)-> void{
          //这一步完成了剪枝过程,如果depth + 1比目前res大,则直接返回,顺便进行了depth+1
          if(!node || ++depth >= res)  return;
				//遇到叶子节点才进行更新答案,返回。
          if(node->left == node->right) {
            res = depth;
            return;
          } 
          dfs(dfs , node->left , depth ); 
          dfs(dfs , node->right , depth ); 
        };
        dfs(dfs , root , 0);
        return res;
    }
};

965. 单值二叉树

思路:自底向上,调用自身

边界条件:

  • 如果碰到叶子节点了,说明过程中都正确,返回true;(可以去掉)
  • 空节点返回true

判断逻辑:

  • 左子树存在时如果值不等于父节点值,返回false。右子树同理
class Solution {
public:
    bool isUnivalTree(TreeNode* root) {
        if(!root)  return true;    
        if(root->left && root->left->val != root->val) return false; 
        if(root->right && root->right->val != root->val) return false; 
        return isUnivalTree(root->left) && isUnivalTree(root->right);
    }
};

100. 相同的树

思路:遍历两颗二叉树,把值存到数组里即可,存值的时候如果遇到空节点,也要存个范围之外的值代替,如样例:

image-20241129170802783

本题范围为-10^4 <= Node.val <= 10^4 , 遇空节点存1e5即可。

自顶向下的前序遍历方法:

class Solution {
    void dfs(TreeNode* root , vector<int>& vec){
        if(!root) {
          vec.push_back(1e5);
          return;
        }
        vec.push_back(root->val);
        dfs(root->left , vec );
        dfs(root->right , vec );
    }
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        vector<int> res1;
        vector<int> res2;
        dfs(p ,res1), dfs(q , res2);
        return res1 == res2 ;
    } 
};

考虑自底向上的方法:

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
      if(!p || !q)  return p == q; //同时为空才行
      
      return p->val == q->val && isSameTree(p->left , q->left) && isSameTree(p->right , q->right);//自底向上
    } 
};

101. 对称二叉树

思路: 在上一题【100. 相同的树】的基础上稍加改动

判断两棵树相同的代码中,把左右稍改即可变为判断镜像是否相同。

return p->val == q->val && isSameTree(p->left, q->right) && isSameTree(p->right, q->left);

本题已经说了根节点非空,判断根的左右子树是否镜像相同即可。

class Solution {
    // 在【100. 相同的树】的基础上稍加改动
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (p == nullptr || q == nullptr) {
            return p == q;
        }
        return p->val == q->val && isSameTree(p->left, q->right) && isSameTree(p->right, q->left);
    }

public:
    bool isSymmetric(TreeNode* root) {
        return isSameTree(root->left, root->right);
    }
};


951. 翻转等价二叉树

思路:

递归,要么反转要么不反转,有一种情况满足就行,即:

不反转:左和左比,右和右比 或 反转:左和右比,右和左比,翻译如下:

        bool ll = dfs(dfs , p->left , q->left);
        bool lr = dfs(dfs , p->left , q->right);
        bool rl = dfs(dfs , p->right , q->left);
        bool rr = dfs(dfs , p->right , q->right);

        return (ll && rr) || (lr && rl);
class Solution {       
public:
    bool flipEquiv(TreeNode* root1, TreeNode* root2) {
      auto dfs = [&](auto&& dfs , TreeNode* p , TreeNode* q){
        if(!p || !q)  return p == q;
        if(p->val != q->val )  return false;

        bool ll = dfs(dfs , p->left , q->left);
        bool lr = dfs(dfs , p->left , q->right);
        bool rl = dfs(dfs , p->right , q->left);
        bool rr = dfs(dfs , p->right , q->right);

        return (ll && rr) || (lr && rl);
      };
      return dfs(dfs , root1 , root2);     
    }
};

1379. 找出克隆二叉树中的相同节点

思路:

我们直接在 getTargetCopy 内部调用它自己,执行递归(先序遍历):

  • 如果 original 是空节点,返回空。

  • 如果 original=target(注意这里比较的是节点,不是节点值),说明我们找到了对应的节点,返回 cloned。

  • 否则递归 original 和 cloned 的左子树,如果返回值 leftRes 不为空,说明 target 在左子树中,返回 leftRes。

  • 否则递归 original 和 cloned 的右子树,由于题目保证 target 一定在二叉树中,所以直接返回递归右子树的返回值。

我们比较的是节点而不是节点值(例如 C++ 比较的是地址),所以下面的代码也适用于树中有值相同节点的情况(本题的进阶问题)。

class Solution {
public:
    TreeNode *getTargetCopy(TreeNode *original, TreeNode *cloned, TreeNode *target) {
      
        if (original == nullptr || original == target) {
            return cloned;
        }
        auto left_res = getTargetCopy(original->left, cloned->left, target);
        if (left_res) {
            return left_res; // 已经找到 target,无需递归右子树
        }
   //由于题目保证 target 一定在二叉树中,所以直接返回递归右子树的返回值。
        return getTargetCopy(original->right, cloned->right, target);
    }
};
posted @ 2024-11-29 18:50  七龙猪  阅读(1)  评论(0)    收藏  举报
-->