代码随想录-二叉树章节技巧总结

递归函数
1、确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:

void traversal(TreeNode* cur, vector<int>& vec)

2、确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:

if (cur == NULL) return;

3、确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:

vec.push_back(cur->val);    // 中
traversal(cur->left, vec);  // 左
traversal(cur->right, vec); // 右

前序遍历顺序为中左右,因此在单层递归逻辑中首先处理当前结点,也就是进行vec.push_back(cur->val); 的操作,最先处理的是根结点,最后被处理的是树最右端的结点。而后续遍历顺序为左右中,因此最先处理的是最左边的结点。

常用的技巧:
1、利用一个pre指针记录当前遍历节点cur的前一个节点。
pre指针的使用技巧在二叉树:搜索树的最小绝对差 (opens new window)和二叉树:我的众数是多少? (opens new window)都提到了,这是常用的操作手段。
2、如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树 。
搜索一条边的写法:

if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

例如二叉树:公共祖先问题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。

搜索整个树写法:

left = 递归函数(root->left);
right = 递归函数(root->right);

以对称二叉树为例,
搜素整棵树的写法

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        // 首先排除空节点的情况
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        // 排除了空节点,再排除数值不相同的情况
        else if (left->val != right->val) return false;

        // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
        bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
        bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)
        return isSame;

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};

搜索一条边的写法如下,与搜索整棵树的区别在于:1、当搜索到不满足对称的结点,这种解法会立即返回结果。2、搜索一条边的解法使用的是前序遍历,思想在于若当前结点不满足对称条件,就结束遍历,而搜索整棵树的解法是后续遍历,也可以说是回溯的思想,当某个结点不满足对称条件时,会将false传递到他的父结点,而父节点通过left或right变量进行承接。

class Solution {
public:
    bool travel(TreeNode* leftnode,TreeNode* rightnode){
        if(leftnode==nullptr&&rightnode==nullptr)   return true;
        if(leftnode!=nullptr&&rightnode==nullptr)   return false;
        if(leftnode==nullptr&&rightnode!=nullptr)   return false;
        if(leftnode->val!=rightnode->val)   return false;

        if(!(travel(leftnode->left,rightnode->right)))  return  false;
        if(!(travel(leftnode->right,rightnode->left)))  return  false;
        return true;
    }
    bool isSymmetric(TreeNode* root) {
        return travel(root->left,root->right);
    }
};

3.终止条件的设立,通常都是指针遍历到空指针(nullptr)时设立终止条件:
1、若仅仅需要关心不为空的结点,那么终止条件设为if(cur==nullptr) return;,例如在二叉树的前序遍历这一题中的代码如下,因为只需按照前序遍历顺序收集不为空结点的值,因此当指针指向空指针时仅代表此时应当结束递归:

void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右

2、而在需要指针指向空结点时,仍需进行一些判断时,此时的终止条件就需要适时进行变化,例如也是在对称二叉树这一题中,终止条件设立如下,这里当处理到空结点时做的就不仅仅是递归终止,而是根据不同的leftnode和rightnode的情况返回不同的函数返回值,这里的终止条件逻辑为当leftnode和rightnode全为空指针时代表两结点相等,但此时已经遍历至空结点,因此应该结束递归,故返回true,当leftnode和rightnode只有一个为空结点或leftnode和rightnode的值不相等时显然两结点不相等,此时应当终止递归,返回false,而终止条件下面的代码就是要去处理单层逻辑的代码了:

if(leftnode==nullptr&&rightnode==nullptr)   return true;
if(leftnode!=nullptr&&rightnode==nullptr)   return false;
if(leftnode==nullptr&&rightnode!=nullptr)   return false;
if(leftnode->val!=rightnode->val)   return false;

4、前序遍历包含遍历即处理的思想,且遍历完左节点回到中间结点时有一个回退的操作,而后序遍历则是先遍历左右结点,根据左右结点的结果得到中间结点的结果,因此要注意两种遍历方法的思想。
在求二叉树的最大深度这道题中,后续遍历和前序遍历的写法如下,要注意前序遍历中的当遍历完左节点回退回根结点时的回溯操作,这个操作在很多道题中都有体现:

//后序遍历,先遍历左右结点,然后根据左右结点得到中间结点的值,这里的求深度相当于先求根结点左孩子的深度,求法就是从底向上进行回溯。再求根结点右孩子的深度,最后求根结点深度。
class solution {
public:
    int getdepth(TreeNode* node) {
        if (node == NULL) return 0;
        int leftdepth = getdepth(node->left);       // 左
        int rightdepth = getdepth(node->right);     // 右
        int depth = 1 + max(leftdepth, rightdepth); // 中
        return depth;
    }
    int maxDepth(TreeNode* root) {
        return getdepth(root);
    }
};

//前序遍历
class Solution {
public:
    int maxdepth=1;
    void travel(TreeNode* root,int curdepth){
        if(root==nullptr)   return;
        if(curdepth>maxdepth)   maxdepth=curdepth;
        if(root->left){
            curdepth++;
            travel(root->left,curdepth);
            curdepth--;
        }
        if(root->right){
            curdepth++;
            travel(root->right,curdepth);
            curdepth--;
        }
    }

    int maxDepth(TreeNode* root) {
        if(root==nullptr)   return 0;
        int curdepth=1;
        travel(root,curdepth);
        return maxdepth;
    }
};

5.当处理的二叉树是二叉搜索树,要注意到中序遍历(左中右)得到的结点值序列应该是单调不减的这一性质,在二叉搜索树中的搜索,验证二叉搜索树,二叉搜索树中的众数这几道题中可以体现;另外要注意,因为二叉搜索树是有序的,所以可以利用这一点来控制树的遍历方向,在二叉搜索树中的插入操作可以体现出来。

6、充分反映出递归思想的题目,可以看看修剪二叉搜索树,这道题去除范围外的节点使用的就是前序遍历,使用递归很好的去除了不需要的节点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值