二叉树刷题小结

        跟着carl哥刷完了二叉树的力扣题单,收获满满。但是就像carl哥说的那样,刷题不是目的,得总结出自己写题的一套方法论,于是趁热打铁。代码很多是自己的想法,也许是灵感迸发,也许是题解启迪,但希望能悟出自己的东西。

        树中的基本概念:

        结点: 树中的一个独立单元。

        结点的度:结点拥有的子树数量。

        树的度:各结点中度的最大值。

        叶子:度为0的结点,也叫终端结点。K,L,F,G等

        非终端结点:度不为0的结点。

        双亲与孩子:结点子树的根称为该结点的孩子,该结点称为孩子的双亲。B的双亲是A,B的孩子有E,F。

        兄弟:同一个双亲的孩子之间互称兄弟。H,I,J互称兄弟。

        祖先:从根到该结点所经历分支上的所有结点。M的祖先为H,D,A。

        子孙:以某结点为根的子树中的任一结点都称为该结点的子孙。B的子孙有:E,K,L,F。

        层次:从根开始定义,根为第一层,根的孩子为第二层,以此类推。

        堂兄弟:同一层的结点互为堂兄弟。

        树的深度:树中结点的最大层次称为树的深度或者高度。

        满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。若满二叉树深度为k,就有2^k-1个结点。

         完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。

        二叉搜索树:

        前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

        平衡二叉搜索树:平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

        存储方式:链表和数组。链表是通法,缺点是空间利用率低。数组通常只用于存储满二叉树。

        二叉树的遍历方式:通常是DFS根BFS。 以下的dfs只写递归法实现。我感觉把dfs掌握就已经很不错了。而且dfs给人一种神秘精致的美感......

  • 深度优先遍历
    • 前序遍历
    • 中序遍历
    • 后序遍历
  • 广度优先遍历
    • 层次遍历

        二叉树的定义:力扣上面的二叉树定义一般是这样:其中TreeNode(int x)只需要填写参数x就可以构建一个val为x,左右孩子为空的二叉树结点。如果不这样写的话,单独写TreeNode *root,我们还需要去给val,left,right的值,要不然就是一个野指针了。

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

二叉树解题思路:

  •  如果基于递归去结题,不用纠结于每一步会怎么样,多想几步脑袋可能就乱了。只要代码的边界条件和非边界条件的逻辑写对了,其他的事情交给数学归纳法就好了。也就是说,写对了这两个逻辑,你的代码自动就是正确的了,没必要想递归是怎么一层一层走的。
  • 节选灵神的话,给迷茫时的自己一点提醒。

二叉树的遍历:

        递归三部曲

  • 确定递归函数的参数和返回值
    • 返回值一般有void与具体返回值。void一般用于全局搜索,有具体返回值的话就很有可能要重复调用自己了。
  • 确定终止条件
    • 终止条件一般是判断树是否为空了。一般放在最上面的位置。
  • 确定单层递归的逻辑
    • 这里注意 可以重复调用这个函数,这就是为什么首先要确定参数与返回值!
三种基础遍历:

        三种遍历方式其实就是访问根节点的时机不同。在左右之前访问就是前序遍历,先左就是中序遍历,左右之后就是后序遍历。

二叉树的前序遍历


        144. 二叉树的前序遍历

class Solution {
public:
    vector<int> ans;
    void dfs(TreeNode * root)
    {
        if(!root) return ;
        ans.push_back(root->val);
        dfs(root->left);
        dfs(root->right);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        dfs(root);
        return ans;
    }
};
二叉树的后序遍历

145. 二叉树的后序遍历        

class Solution {
public:
    vector<int> ans;
    void dfs(TreeNode* root)
    {
        if(!root) return ;
        dfs(root->left);
        dfs(root->right);
        ans.push_back(root->val);
    }

    vector<int> postorderTraversal(TreeNode* root) {
        dfs(root);
        return ans;
    }
};
二叉树的中序遍历

94. 二叉树的中序遍历

class Solution {
public:
    vector<int> vec;
    void dfs(TreeNode* root)
    {
        if(root)
        {
        dfs(root->left);
        vec.push_back(root->val);
        dfs(root->right);
        }
    }

    vector<int> inorderTraversal(TreeNode* root) {
        dfs(root);
        return vec;
    }
};
层次遍历:       
二叉树的层次遍历

102. 二叉树的层序遍历

        BFS是有模板的,所以如果用到层次遍历的话题目就比较简单了,直接套模板即可。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        vector<int> vec;
        queue<TreeNode*> q;
        if(!root) return ans;
        q.push(root); //先加入根节点
        while(q.size()) // 如果队列不空的话
        {
            int sum = q.size();  //这里需要记录上一个结点引入的新节点个数
            ********* 每一层的具体逻辑 *********
            vec.clear(); 
            for(int i = 0;i<sum;i++)
            {
                auto t = q.front(); // 取结点
                q.pop();  // 取完就可以删了
                vec.push_back(t->val);  //对该结点的具体操作
                // 加入左右结点
                if(t->left) q.push(t->left); 
                if(t->right) q.push(t->right);
            }
            ans.push_back(vec);
        }
        return ans;
    }
};

二叉树的性质:   

二叉树的最大深度
  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)

而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。

104. 二叉树的最大深度  

        用后续遍历,先左右,最后在中 +1

① 确定函数的返回值和参数:要求的是最大深度,所以返回值是一个int类型的变量。参数就是一个结点。

② 终止条件:遇到空结点就要返回了。

③ 只考虑最小的子问题:一颗二叉树。它的最大深度就是左右子树中的最大高度加上根节点的高度1,所以就是max(left,right)+1就行了。

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

其实精简的写法是: 只是这样看的比较看不懂。但是后面只要是root->left跟root->right,基本上就是递归到左右子树了。

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

二叉树的最小深度

111. 二叉树的最小深度

        ① 返回值是int,传入结点即可。

        ② 遇到空就返回了

        ③ 当左右子树都不为空的时候,答案取min+1,如果其中有一颗子树为0,那另一边一定是最小深度(因为深度最小为1不能为0 )

class Solution {
public:
    int dfs(TreeNode* root)
    {
        if(!root) return 0;
        int mn_l = dfs(root->left);
        int mn_r = dfs(root->right);
        if(mn_l!=0 && mn_r!=0) return min(mn_l,mn_r)+1;
        else return max(mn_l,mn_r)+1;
    }
    int minDepth(TreeNode* root) {
        return dfs(root);
    }
};

相同的树

100. 相同的树

          后序遍历。(不是说p->val == q->val放在前面就是前序遍历的)

        ① 返回值就是 bool,传入值是左右两棵子树

        ② 如果两棵树中有一颗为空了,只有另外一颗也为空才可以返回1,其他情况都返回0

        ③ 先判断当前两棵树的val,在递归判断左子树跟右子树。

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. 对称二叉树

        前序后序都可以。

        其实跟上一题判断二叉树相等是一样的。

        ① 这里可以发现,一棵树的根节点是不用去比较的,只需要看树的左右结点的关系就行。返回值是bool,传入值是左子树根右子树

        ② 如果我们在比较左右子树的时候,如果遇到有空的情况,如果两棵树都为空,这种情况就返回1,其余的情况肯定都是返回0了。

        ③如果两棵树都存在,先判断。两棵树的val是否相等,然后判断左子树的右孩子跟右子树的左孩子是否满足关系,在判断右子树的左孩子跟左子树的右孩子是否满足。

class Solution {
public:
    bool dfs(TreeNode *p,TreeNode *q)
    {
        if(!p||!q) return p == q;
        return p->val == q->val&&dfs(p->left,q->right) && dfs(p->right,q->left);
    }
    bool isSymmetric(TreeNode* root) {
        return dfs(root->left,root->right);
    }
};
完全二叉树的结点个数

后序遍历:左右遍历完+1(本身)

222. 完全二叉树的节点个数

① 返回int,表示以该根节点的树的结点个数。传入根节点就行。

② 当遇到空结点的时候就返回。空结点的结点个数为0,返回0。

③ 考虑最小的一棵树。它的结点个数就是 左子树的结点个数+右子树的结点个数+1(本身)。

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(!root) return 0;
        return countNodes(root->left)+countNodes(root->right)+1;
    }
};
平衡二叉树

110. 平衡二叉树

其实也算是求高度。应该是用后序遍历去解决问题。

①参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。如果发现当前树的左右子树已经不满足平衡条件了,我们直接返回-1就行了。

②递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0

③ 如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

class Solution {
public:
    int dfs(TreeNode * root)
    {
        if(!root) return 0;
        int l_h = dfs(root->left);
        if(l_h == -1) return -1;
        int r_h = dfs(root->right);
        if(r_h == -1 ) return -1;
        if(abs(l_h-r_h)>1) return -1;
        return  max(r_h,l_h)+1;
    }
    bool isBalanced(TreeNode* root) {
        return dfs(root) != -1;
    }
};

257. 二叉树的所有路径

        这是一道回溯题目。需要遍历树的每一条根。采用中序遍历即可。

        ① 既然不是一个数的局部属性,所以肯定是要用void去遍历整棵树的了。传入参数就是根节点。

        ② 遇到叶子结点就收集答案回溯了。

        ③ 有点像子集型回溯,每一步递归都先收集节点值,然后递归左右结点。

        dfs里面str+"->"可以自动回溯,就不需要写 str +="->";dfs(root,str);str.erase(str.size()-3,2);了

class Solution {
public:
    vector<string> ans; 
    void dfs(TreeNode * root,string str)
    {
        str+=to_string(root->val);
        if(!root->left&&!root->right)
        {
            ans.push_back(str);
            return ;
        }
        if(root->left) dfs(root->left,str+"->");
        if(root->right) dfs(root->right,str+"->");
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        string str;
        dfs(root,str);
        return ans;
    }
};

左叶子之和

        后序遍历。先深入叶子结点再回代出值。

404. 左叶子之和

关键:判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。

① 判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int使用题目中给出的函数就可以了。

②如果遍历到空节点,那么左叶子值一定是0

③当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        int sum = 0;
        if(!root) return sum;
        if(root->left&&!root->left->left && !root->left->right) //存在左结点,且该结点是叶子结点
            sum += root->left->val;
        return sum +sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right);
    }
};
找树左下角的值

513. 找树左下角的值

        这个题目用递归去思考会发现最后一个树的情况有很多情况:首先得是最后一层,然后得是最左边。不一定是左子树,也可能是右子树是最左边。所以用bfs很容易解决。

        层次遍历也需要灵活应用的。这里让t指向最后一个结点就行。所以先右边再左边。这样可以保证t指向的是层次遍历的最后一个元素。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> q;
        TreeNode * t;
        q.push(root);
        while(q.size())
        {
            t = q.front();
            q.pop();
            if(t->right) q.push(t->right);
            if(t->left) q.push(t->left);
        }
        return t->val;
    }
};
路径总和

112. 路径总和

直观想法一直收集结点的值,然后到叶子结点判断就可以了。可以用void全局搜,也可以就返回bool,因为子树满足性质,整棵树也满足。

① 返回值就可以是bool,传入参数有:根节点,目前值sum,目标值tar

② 叶子节点比对答案,反正0或1,如果当前root不存在,也返回0

③ 每到一个结点就收集当前值即可。

        if(!root) return 0 ;不可以不要。一些结点只有左孩子或者只有右孩子的话,当进入另一个没有孩子的结点就会发生错误。

class Solution {
public:
    bool dfs(TreeNode * root,int sum,int tar)
    {
        if(!root) return 0 ;
        sum+=root->val;
        if(!root->left&&!root->right)
            if(sum == tar) 
                return 1 ;
        return dfs(root->left,sum,tar) || dfs(root->right,sum,tar);
    }
    bool hasPathSum(TreeNode* root, int tar) {
        if(!root) return 0;
        return dfs(root,0,tar);
    }
};

二叉树的修改与构造:

        从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树

        给定了中序,再给定前序或者后序,都可以重构建一颗二叉树。

        原理是:前序跟后序可以很快速找到根节点的值,通过这个值就可以再中序确定左右子树的区间。然后一直递归下去构建就可以了。

        ① 传入参数:中序数组与后序数组。

        ② 终止条件:后序数组为空了。(这样就找不到根节点的值了)这时候返回空即可。

        ③ 在中序遍历中找到左右子树所在的区间,再根据区间的大小在后序遍历中找到左右子树后序遍历的区间。当前的根节点左右子树分别等于同左或者同右的中序,后序数组的返回值即可。

        注意:

        vector的有参构造:第一个是begin(),第二个是end()。也就是说传入的范围是左开右闭的。end()是最后一个元素的下一个元素。

        post区间的划分可以根据in区间的去做。因为划分完的左右区间大小是对应的。

class Solution {
public:
    TreeNode* trav(vector<int>& inorder, vector<int>& postorder)
    {
        if(!postorder.size()) return NULL;
        int rootVaule = postorder.back();
        postorder.pop_back();
        auto root = new TreeNode(rootVaule);
        int idx ;
        for(idx = 0;idx<inorder.size();idx++)
            if(inorder[idx] == rootVaule)
                break;
        vector<int> leftinorder(inorder.begin(),inorder.begin()+idx);
        vector<int> rightinorder(inorder.begin()+idx+1,inorder.end());
        vector<int> leftpostinorder(postorder.begin(),postorder.begin()+leftinorder.size());
        vector<int> rightpostinorder(postorder.begin()+leftinorder.size(),postorder.end());
        root->left = trav(leftinorder,leftpostinorder);
        root->right = trav(rightinorder,rightpostinorder);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(!inorder.size()) return NULL;
        return trav(inorder,postorder);
    }
};
        最大二叉树

654. 最大二叉树

        思路跟上面一样。区间建树的套路感觉就是这样的。

        感觉加上if(nums.size() == 1) return root;比较好,因为剩余一个元素的时候就直接返回就行了,相当于剪枝了。

class Solution {
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        if(!nums.size()) return nullptr;
        int idx = max_element(nums.begin(),nums.end()) - nums.begin();
        TreeNode * root = new TreeNode(nums[idx]);
        if(nums.size() == 1) return root;
        vector<int> l_n(nums.begin(),nums.begin()+idx);
        vector<int> r_n(nums.begin()+idx+1,nums.end());
        root->left = constructMaximumBinaryTree(l_n);
        root->right = constructMaximumBinaryTree(r_n);
        return root;
    }
};
 翻转二叉树

226. 翻转二叉树

        前序或者后续都可以,这里用前序。

        ① 翻转左右孩子是一个局部上的性质,但是通过递归可以扩展到整棵树。所以传入参数是一个根结点,返回的参数是根节点。

        ② 遇到空结点就可以返回了。

        ③ 我们不用考虑当前的根节点左右孩子是什么情况,是空还是有值直接交换就可以了。所以具体逻辑就是swap即可。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == 0) return root;
        swap(root->left,root->right);
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};
合并二叉树

617. 合并二叉树

① 返回的是一个根结点,传入左右子树根结点就行。

② 三种返回情况:如果左右都为空,直接返回空就行,如果左空右不空,返回root1就行,如果右空左不空,返回root2就行。

③ 如果都不空,直接加上val就行。然后递归建左子树跟右子树就行。

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(!root1 && !root2) return nullptr;
        if(root1 && !root2) return root1;
        if(!root1 && root2) return root2;
        root1->val = root1->val + root2->val;
        root1->left = mergeTrees(root1->left,root2->left);
        root1->right = mergeTrees(root1->right,root2->right);
        return root1;
    }
};

二叉搜索树的性质

        二叉搜索树很重要的一条性质就是:中序遍历完之后得到的是一个有序的数组。

验证二叉搜索树

98. 验证二叉搜索树

递归有两种遍历方式: 

        先序遍历: 这种就是看当前结点是否在左右结点的区间范围内,如果在,那就是二叉搜索树。根节点的左右结点范围是负无穷到正无穷。递归左孩子的时候就是将右边界换成当前值,递归有孩子的时候将左边界换为当前值。

        ① 返回值为 0或1,传入参数为根节点和左右孩子值作为一个区间左右值

        ② 终止条件:如果当前结点为空,返回1,如果当前结点的val不在区间中,返回0

        ③ 依次递归左右孩子即可。

class Solution {
public:
    bool isValidBST(TreeNode* root,long left = LONG_MIN,long right = LONG_MAX) {
     if(!root) return 1;
     long x = root->val;
     return left < x&&x<right &&isValidBST(root->left,left,x)&&isValidBST(root->right,x,right);
    }
};

        也可以采用中序遍历,这里使用了一种类似双指针的方法:用一个指针pre记录当前指针cur的前一个,然后根据中序遍历的特点比较cur的val与pre的val即可。

        这里满足三个条件才可以说是一棵二叉搜索树,所以说不能isValidBST(cur->left)返回True了,就直接返回True。只有左中判断完了,最后右如果也满足才可以返回True.

class Solution {
public:
    TreeNode * pre = NULL;
    bool isValidBST(TreeNode* cur) {
        if(!cur) return 1;
        if(!isValidBST(cur->left)) return 0;
        if(pre && cur->val <= pre->val) return 0; 
        pre = cur;
        return isValidBST(cur->right); 
    }
};

        这里解释一下如何理解pre指针。以下图为例:采用中序遍历,到第一个结点会先进入递归左子树的函数,还遇不到pre那一行。等到了1结点以后,由于pre为空,所以不会比较cur与pre,程序再往下走就是pre = cur了。这时候pre已经指向了1结点。左右子树都为空,所以从1回溯到5是True。5的左递归走完就到比较当前结点了(因为是中序遍历),这时候比较的是1与5的大小了。此时满足条件:5>1,然后pre = 5,然后cur会右递归再一直左递归到3,然后比较pre与cur .此时pre = 5,cur = 3,不满足条件,返回False。

        为什么 if(pre && cur->val <= pre->val) return 0;  放在pre = cur;上面呢?首先pre得不为空才可以比较与cur的值,所以pre一定要与cur->val <= pre->val放在一起。然后一定是先比较两个指针的值再移动指针。要不然就是自己与自己比较了。

二叉搜索树中的搜索

700. 二叉搜索树中的搜索

        ① 可以用返回值为void 去全局搜,相当于无视二叉搜索树的条件,当成普通树去搜。显示是可以的。

class Solution {
public:
    TreeNode* rt = NULL;
    void dfs(TreeNode* root, int val)
    {
        if(!root) return ;
        if(root->val == val) 
            rt = root;
        dfs(root->left,val);
        dfs(root->right,val);
    }
    TreeNode* searchBST(TreeNode* root, int val) {
        dfs(root,val);
        return rt;
    }
};

        ② 也可以按照返回值是结点的方式去搜:根据二叉搜索树的性质:根节点的值大于左孩子的值,小于右孩子的值。所以先比较根节点,然后搜左右就行了。相当于是先序遍历。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if(!root) return root;
        if(root->val == val ) return root;
        else if( root->val > val) return searchBST(root->left,val);
        else return searchBST(root->right,val);
    }
};
二叉搜索树的最小绝对差

530. 二叉搜索树的最小绝对差

① 如果按照int返回,空结点感觉不是很好处理。所以这题我们用void返回全局搜就行了。

②遇到空结点就返回。

③ 按照中序遍历,每次记录更新pre跟cur的差的最小值就行了。

class Solution {
public:
    TreeNode * pre = nullptr;
    int ans = INT_MAX;
    void dfs(TreeNode * cur)
    {
        if(!cur) return ;
        dfs(cur -> left);
        if(pre)
            ans = min(ans,abs(cur->val-pre->val));
        pre = cur;
        dfs(cur->right);
    }
    int getMinimumDifference(TreeNode* root) {
        dfs(root);
        return ans;
    }
};
二叉搜索树中的众数

501. 二叉搜索树中的众数

        最直观的做法是不是直接中序遍历出有序数组然后找众数呢?但是我们可以用双指针的办法判断前一个数与后一个树是否相等,这样也可以。

        ① 由于是整个树上找,不是局部性质,所以用返回值void搜就行了。

        ② 遇到空结点直接返回就行了。

        ③ 如果pre->val 跟cur->val 相等的话,当前计数器++,如果不相等的话,计数器变1。然后更新一下最大值就行了。

        发现了吗? 一般二叉搜素树中用双指针的话pre = cur 都是写在中间结点逻辑处理完以后的。

class Solution {
public:
    TreeNode * pre = nullptr;
    vector<int> ans;
    int max_count = INT_MIN;
    int cur_count = 1;
    void dfs(TreeNode* root)
    {
        if(!root) return ;
        dfs(root->left);
        if(pre && pre->val == root->val)
            cur_count += 1 ;
        else cur_count = 1;
        if(cur_count > max_count)
        {
            max_count = cur_count;
            ans.clear();
            ans.push_back(root->val);
        }
        else if( cur_count == max_count)
            ans.push_back(root->val);
        pre = root;
        dfs(root->right);
    }
    vector<int> findMode(TreeNode* root) {
        dfs(root);
        return ans;
    }
};
二叉树中的双指针法写法总结:

        前序:


    void dfs(TreeNode * root)
    {
        if(!root) return ;
        ans.push_back(root->val);
        ***********逻辑代码*************
        if(pre)
            cout <<"pre:"<<pre->val <<"cur:"<<root->val<<endl;
        pre = root;
        dfs(root->left);
        dfs(root->right);
    }

        中序:


      void dfs(TreeNode* root)
    {
        if(root)
        {
            dfs(root->left);
            vec.push_back(root->val);
        ***********逻辑代码*************
            if(pre)
                cout<<"pre:"<<pre->val<<"cur:"<<root->val<<endl;
            pre = root;
            dfs(root->right);
        }
    }

        后序:

    void dfs(TreeNode* root)
    {
        if(!root) return ;
        dfs(root->left);
        dfs(root->right);
        ***********逻辑代码*************
        if(pre)
        cout<<"pre:"<<pre->val<<"cur:"<<root->val<<endl;
        ans.push_back(root->val);
        pre = root;
    }

二叉树公共祖先问题


236. 二叉树的最近公共祖先

        最直观的想法是:从两个结点出发,一直向上找,两个分支第一次相遇的结点就是最近的公共祖先。但是如何实现二叉树自下而上的寻找呢?回溯的过程就是二叉树自下而上的过程。

        补充一个总结: 

        最近公共祖先问题要对递归回溯的结果做一个总的判断:

        ① 我们可以用TreeNode* 作返回值,传入参数就是三个结点值。

        ②当遇到空结点时,返回空,当遇到当前结点等于p或者q的时候,说明已经找到了最近公共祖先了。直接然后root即可。

        ③采用后序遍历,先左后右,利用两个遍历接收。最后做逻辑处理。这种方式还是会遍历整棵树。对于根节点来说,如果答案已经在左子树,由于是后序遍历,右子树也需要遍历完才可以判断结果。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q || !root) return root;
        auto leftT = lowestCommonAncestor(root->left,p,q);
        auto rightT = lowestCommonAncestor(root->right,p,q);
        if(leftT && rightT) return root;
        else if (!leftT) return rightT;
        return leftT;
    }
};

        图示如下:

总结:后序遍历更像是把遍历完所有情况再做判断。这题中具体表现为:遍历完所有情况以后将结果一级一级向上返回。

二叉搜索树中的最近公共祖先

235. 二叉搜索树的最近公共祖先

由于二叉搜索树的特性:左<中<右,我们可以利用这个性质直接去完成。

          这里不用定义两个变量去接受返回值,这里是搜索一条边的写法。

class Solution {
public:
    TreeNode* dfs(TreeNode* root,TreeNode *p,TreeNode* q)
    {
        if(!root) return NULL;
        if(root->val > p->val && root->val <q->val)
            return root;
        if(root->val > p->val &&root->val >q->val)
            return dfs(root->left,p,q);
        if(root->val <p->val && root->val <q->val)
            return dfs(root->right,p,q);
        return root;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return dfs(root,p,q);
    }
};

二叉搜索树的修改与构造

二叉搜索树中的插入操作

        插入操作可以不用想的那么麻烦,按照二叉搜索树的规则一直去比较左右大小然后递归下去,遇到空结点直接插入就行了。

        如果返回值是void的话,只需要记录下父节点就行。怎么记录呢?还是用上面的模板就行了。

如果想记录上一个结点的话,用双指针就行了。

class Solution {
public:
    TreeNode * pre = NULL;
    bool flag = 0;
    void dfs(TreeNode* &root,int val)
    {
        if(flag) return ;
        if(!root)
        {
            TreeNode * t = new TreeNode(val);
            if(pre->val >val) pre->left = t;
            else pre->right = t;
            flag = 1;
            return ;
        }
        pre = root;
        if(root->val > val)
            dfs(root->left,val);
        if(root->val < val)
            dfs(root->right,val);
    }
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(!root)
        {    
            TreeNode * root = new TreeNode(val);
            return root;
        }
        dfs(root,val);
        return root;
    }
};

返回值是TreeNode* 的写法:

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(!root)
        {
            auto t = new TreeNode(val);
            return t;
        }
        if(root->val > val) root->left =  insertIntoBST(root->left,val);
        else if(root->val < val)root->right =  insertIntoBST(root->right,val);
        return root;
    }
};

        总之,二叉搜索树的插入还是很简单的,一句话:找到空位去插就行了,这种方法得到的一定是符合题意的。但是二叉搜索树的删除就很难了。

        删除二叉搜索树中的结点

450. 删除二叉搜索树中的节点

        直接按结点返回即可,利用二叉搜索树的性质。

        主要是要分类讨论一下删除后的情况:

  •  如果是空结点,返回空即可。
  • 如果不是空结点,要判断当前结点值是否与key相等
    • 如果相等:
      • 该结点是是叶子结点:直接返回NULL即可
      • 如果该结点只有左孩子:直接返回右孩子即可
      • 如果该结点只有右孩子:直接返回左孩子即可
      • 如果该结点有左右孩子,需要把左孩子放到右孩子的最左孩子上,再将右孩子返回
    • 如果不相等:继续递归左右孩子即可
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(!root) return root;
        if(root->val == key)
        {
            if(root->left == nullptr&& root->right == nullptr)
            {
                delete root;
                return nullptr;
            }
            else if(root->left == nullptr)
            {
                auto t = root->right;
                delete root;
                return t;
            }
            else if(root->right == nullptr)
            {
                auto t = root->left;
                delete root;
                return t;
            }
            else
            {
                auto t = root->right;
                while(t->left) t = t->left;
                t->left = root -> left;
                auto temp = root;
                root = root->right;
                delete temp;
                return root;
            }
        }
        if(root->val > key) root -> left = deleteNode(root->left,key);
        if(root->val < key) root -> right = deleteNode(root->right,key);
        return root;
    }
};
修剪二叉搜索树

        669. 修剪二叉搜索树

        可以用常规思路:删除结点的做法。但一定要注意用后序遍历!!!即最后处理根结点。要不然根节点不满足直接删了,后面递归左右结点,根结点都没有了怎么递归呢?

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root) return root;
        root->left = trimBST(root->left,low,high);
        root->right = trimBST(root->right,low,high);
        if(root->val >high || root->val < low)
        {
            if(!root->left && !root->right) return nullptr;
            else if(!root->left) return root->right;
            else if(!root->right) return root->left;
            else
            {
                auto t = root->right;
                while(t->left) t = t->left;
                t->left = root->left;
                return root->right;
            }
        }
        return root;
    }
};

        为什么上一题是先序遍历? 其实也不算先序遍历。先序是无脑先中间再左再右。二叉搜索树是每次根据节点值只会搜左边或者右边。

        也可以这样想:不满足的结点直接移除,返回后面的结点就行了。

        如果当前结点值小于low,则接入右孩子满足的值。>high同理

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (root == nullptr) return nullptr;
        if (root->val < low) return trimBST(root->right, low, high);
        if (root->val > high) return trimBST(root->left, low, high);
        root->left = trimBST(root->left, low, high);
        root->right = trimBST(root->right, low, high);
        return root;
    }
};

        看上去有点抽象,其实也不算删除结点,而是选择结点。也是根据二叉搜索树的性质:

low = 1, high = 2,当前结点为3,递归进入左子树,最后左子树都满足,返回的是1为根的子树,4都不会走,直接结束了。所以返回的就是1为根的子树。

将有序数组转换为二叉搜索树

        

108. 将有序数组转换为二叉搜索树

区间构造树是有模板的。按照前面的模板来就行了。

class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        if(!nums.size()) return nullptr;
        if(nums.size() == 1) return new TreeNode(nums[0]);
        int val = nums[nums.size()/2];
        auto t = new TreeNode(val);
        vector<int> L(nums.begin(),nums.begin()+nums.size()/2);
        vector<int> R(nums.begin()+nums.size()/2+1,nums.end());
        t->left = sortedArrayToBST(L);
        t->right = sortedArrayToBST(R);
        return t;
    }
};
把二叉搜索树转化成累加树

538. 把二叉搜索树转换为累加树

        其实能看图看出:从最右结点一直累加结点值。

class Solution {
public:
    int sum;
    void dfs(TreeNode * root)
    {
        if(!root) return ;
        dfs(root->right);
        sum+=root->val;
        root->val = sum;
        dfs(root->left);
    }
    TreeNode* convertBST(TreeNode* root) {
        dfs(root);
        return root;
    }
};

补充: 如果将答案集合定义在全局:那么在全局中,那每次dfs之后都要记得回溯,即恢复到没有搜那个点的状态。但是如果定义在函数中就不用回溯,系统栈自动帮你回溯了。前提是不要用引用参数(&)。

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值