树常见题目

一、树的数据结构

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

二、树的递归

一般与深度优先递归类似。

三、专项练习

104、二叉树的最大深度

给了一个二叉树,求它的最大的深度(指的是从根节点到子节点的最长路径上的节点总数)。


class Solution {
public:
    int maxDepth(TreeNode* root) {
        //深度遍历:深度为最长路径上的节点数目
        return dfs(root,0);


    }
    int dfs(TreeNode* root, int cur_depth){
        //深度遍历
        //特判
        if(root==nullptr)return cur_depth;
        //否则的话分别对两个子节点求出其最大值
        int left_max = dfs(root->left,cur_depth+1);
        int right_max = dfs(root->right,cur_depth+1);
        int ret = max(left_max,right_max);
        return ret;
    }
};

踩坑:
1、注意这个结构是* 因此需要使用left时候需要root->left
2、注意到C++的空时nullptr,不是null

110、平衡二叉树

给了一个树,判断它是否是平衡二叉树。(平衡二叉树的条件是:左右子树的高度差不超过1,并且它的左右子树都是平衡二叉树)。

解题:dfs函数,递归的判断每一个节点是否为平衡二叉树。(要求:当前节点子树高度差<=1&&当前节点左右节点递归的是平衡二叉树)。
再写一个求高度的函数。


class Solution {
public:
    bool isBalanced(TreeNode* root) {
        //给一个根节点,判断左子树的高度和右子树的高度是否差小于=1
        
        //否则递归的判断左右子树的高度是否只相差1。
        return dfs(root);
    }

    bool dfs(TreeNode* root){
        //递归的判断一个树是否是平衡的
        if(root==nullptr)return true;
        else {
            if(dfs(root->left)&&dfs(root->right))
            {
                if(abs(hight(root->left)-hight(root->right))<=1)return true;
            }
            return false;
        };
    }


    int hight(TreeNode* root){
        //求它的高度
        if(root==nullptr)return 0;
        int l = hight(root->left);
        int r = hight(root->right);
        return 1+max(l,r);
    }
};
543、二叉树的最长直径

给了一个二叉树,求它的最长的直径。直径:二叉树上任意两节点的无向距离。

思路:这题重要的是直径的定义,注意到这是二叉树中任意两个点之间的无重复边的最大路径长度。
看到二叉树考虑递归的思路。
从一个根结点出发,要嘛这个全在它的左节点,要嘛路径全在右子树,要嘛这个路径横跨了左右子树(则经过根结点)。
则令每一个返回两个值,一个是它不能和上一层再叠加的最大值,另一个是可以和上一层叠加的,也就是只在某一子树,且经过当前的根结点的一条路径的值。


class Solution {
public:
    int diameterOfBinaryTree(TreeNode* root) {
        //对于某一个根结点来说,这个的值要嘛是它左子树的结果,要嘛是右子树的结果,要嘛是左+右的结果(要经过根节点)。
        //每次需要返回两个结果,1、能让上一层继续叠加的结果2、上一层无法继续参与的值(经过当前根节点的答案)。
        pair<int,int> ret =  getMax(root);
        // cout<<"ret[0]="<<ret.first<<" ret[1]="<<ret.second<<endl;
        return max(ret.first,ret.second)-1;


    }

    pair<int,int> getMax(TreeNode* root){
        //以当前节点为根结点的最大值
        
        //特判:
        if(root==nullptr){
            return pair<int,int>(0,0);
        }else{
            
            pair<int,int> left = getMax(root->left);
            pair<int,int> right = getMax(root->right);
            // cout<<"root = "<<root->val<<endl;
            // cout<<"left[0]="<<left.first<<" left[1]="<<left.second<<endl;
            // cout<<"right[0]="<<right.first<<" right[1]="<<right.second<<endl;
            int sec = 0;
            if(left.first!=0 && right.first!=0){
                sec = max(1+left.first+right.first,0);
            }
            return pair<int,int>(max(left.first,right.first)+1,max(max(left.second,right.second),sec));
        }
    }
};
  • 注意这里使用了pair类型,需要输入两个元素的类型,并且使用.first 与 .second来分别调用两个元素。

官方解题的思路:这个路径不管怎么走,一定是某个节点以及其左右的最深路径的结合(这个节点可能不为根结点),因此对某个节点的处理就是分别求其左子树和右子树的最大深度之和。
对于一棵树来考虑需要对其中每个节点都进行上述计算。

另一种思路:其实是刚刚的pair返回值的改版,只使用一个返回值,另一个通过引用传递传递,返回值表示的是以当前根结点为一个起点的max长度,通过引用传递传递的值是真正的最大值。

    public:
    int diameterOfBinaryTree(TreeNode* root) {
        //或者使用一个的返回值,并且使用一个引用值
        int All_max = 0;
        getmax(root,All_max);
        return All_max-1;
    }
    int getmax(TreeNode* root,int& All_max){
        //返回值为从当该跟根结点为一端到另一个叶子结点的长度
        if(root==nullptr){
            return 0;
        }
        //否则进行处理
        int left = getmax(root->left,All_max);
        int right = getmax(root->right,All_max);
        All_max = max(left+right+1,All_max);
        return max(left,right)+1;
    }

};
437、路径总和

给了一每个节点带有权重的二叉树,求共有多少条从上往下的路径,其上节点总和为所求值。

解题:使用dfs便利每一个节点。再分别从该节点出发,看从它出发的路径上是否可能有和x的路径。

class Solution {
public:
    int pathSum(TreeNode* root, int targetSum) {
        //深度便利法便利一下所有的节点,对每个节点分别计数以其为节点的路径数。
        int cnt = 0;
        dfs(root,cnt,targetSum);
        return cnt;
    }
    void dfs(TreeNode* root,int& cnt,int targetSum){
        //深度便利便利所有节点
        if(root==nullptr)return;
        // cout<<"cur——Node"<<root->val<<endl;
        addNum(root,cnt,targetSum);
        dfs(root->left,cnt,targetSum);
        dfs(root->right,cnt,targetSum);
        return;
    }
    void addNum(TreeNode* root,int& cnt,int targetSum){
        //使用递归寻找
        traceBack(root,cnt,targetSum,0);
    }

    void traceBack(TreeNode* root,int& cnt,int targetSum,int cur){
        if(root==nullptr)return;
        cur = cur + root->val;
        // cout<<"root = "<<root->val<<endl;
        // cout<<"cur = "<<cur<<endl;
        if(cur == targetSum){
            cnt++;
            // cout<<"cnt++,cnt = "<<cnt<<endl;
        }
        traceBack(root->left,cnt,targetSum,cur);
        traceBack(root->right,cnt,targetSum,cur);
        return;
    }

    //从当前根节点出发的路径数目,使用回溯法。
};

方法二、可以省一层递归,第一个方法就进行递归。

   int pathSum(TreeNode* root, int targetSum) {
        //深度便利法便利一下所有的节点,对每个节点分别计数以其为节点的路径数。
        if(root==nullptr)return 0;
        return traceBack(root,targetSum,0)+pathSum(root->left,targetSum)+pathSum(root->right,targetSum);
    }

    int traceBack(TreeNode* root,int targetSum,int cur){
        if(root==nullptr)return 0;
        int cnt = 0;
        cur = cur + root->val;
        if(cur == targetSum){
            cnt++;
        }
        cnt += traceBack(root->left,targetSum,cur);
        cnt += traceBack(root->right,targetSum,cur);
        return cnt;
    }
};
  • 踩坑:注意:如果要用这种方法的话,就需要有个返回值,并且需要将其加上左右节点的结果再返回。

//TODO:使用前缀和的方式求解。

一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析).

101、描述一个二叉树是否对称

这里的对称指的是以中间为对称轴对称。

那么每次输入两个节点,他们处于对称的位置,进行递归的判断他们是否对称。

  • 首先判断这两个节点的值是否为空,是否相等
  • 如果这两个节点对称的话,继续遍历看他们的子节点是否对称,左节点的左和右节点的右,右节点的左和左节点的右。
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root==nullptr)return true;
        return isSymmetric_two(root->left,root->right);
    }
    //每次输入两个节点,判断两个节点是否堆成
    bool isSymmetric_two(TreeNode* left,TreeNode* right){
        if(left==nullptr && right==nullptr)return true;
        if(left==nullptr || right==nullptr)return false;
        return(left->val == right->val)&&isSymmetric_two(left->left,right->right)&&isSymmetric_two(left->right,right->left);
    }
};

判断两个子树是否相等/对称型题目四步法:
(1)如果两个子树都为空指针,则它们相等或对称
(2)如果两个子树只有一个为空指针,则它们不相等或不对称
(3)如果两个子树根节点的值不相等,则它们不相等或不对称
(4)根据相等或对称要求,进行递归处理。

226、翻转二叉树

给了一个二叉树,求它按照对称的方式翻转后的结果。

思路:类似上面的对称二叉树,每次输入左右两个二叉树,把左右两个树互换。
对于左右均存在的情况
首先需要交换两个节点的值,然后再递归的处理两个节点的子节点。
对于左右节点有一个不存在的情况,想要继续交换,就需要首先为空节点初始化一个二叉树节点,为其赋值,那么就和原来一样了。处理完后设置另一边的节点为null,那么注意需要使用&来把节点向上传递。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        //把左右子树交换
        if(root==nullptr)return root;
        changeTwo(root->left,root->right);
        return root;
    }
    void changeTwo(TreeNode*& l,TreeNode*& r){
        //目标,操作后将这两颗树对称的位置值交换
        if(l == nullptr && r == nullptr)return;
        if(l == nullptr){
            l = new TreeNode();
            l->val = r->val;
            changeTwo(l->left,r->right);
            changeTwo(l->right,r->left);
            r = nullptr;

        }else if(r==nullptr){
            r = new TreeNode();
            r->val = l->val;
            changeTwo(l->left,r->right);
            changeTwo(l->right,r->left);
            l = nullptr;
        }else{
            //要确保左右都有值
            int tmp = r->val;
            r->val = l->val;
            l->val = tmp;
            changeTwo(l->left,r->right);
            changeTwo(l->right,r->left);
        }
        
        return;
    }
};

更加简洁的方法:可以通过迭代的交换左右子树来获得。

在这里插入图片描述

  • 我们自下往上看
  • 7的左右子树9与6互换,使得9换到了该层上它的对应位置。
  • 7与2换,使得换了两次的9换到了这层中它对应的对称位置。
  • 依次类推,因此可以递归的交换左右子树。
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        //把左右子树交换
        if(root==nullptr)return nullptr;
        invertTree(root->left);
        invertTree(root->right);
        TreeNode* tmp = root->left;
        root->left = root->right;
        root->right = tmp;
        return root;
    }
};
617、合并两个二叉树

把两个二叉树对应位置的值相加后合成一个新的二叉树。

思路:迭代

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(root1==nullptr&&root2==nullptr)return nullptr;
        if(root1==nullptr)return root2;
        else if(root2==nullptr)return root1;
        else{
            //TreeNode* root = new TreeNode();
            root1->val = root1->val+root2->val;
            root1->left = mergeTrees(root1->left,root2->left);
            root1->right = mergeTrees(root1->right,root2->right);
            return root1;
        }
    }
};
572、另一棵树的子树

给了两个树,求一棵树是否是另一棵树的子树。
写一个方法判断两个树是否相同,如果相同返回true。
如果该子树和所给的子树不同,则递归遍历该子树的子树,看有没有哪个节点能作为根节点和所给的子树同。

class Solution {
public:
    bool isSubtree(TreeNode* root, TreeNode* subRoot) {
        //判断以root为根的和subroot是否相同。
        if(isSame(root,subRoot)==true)return true;
        if(root==nullptr)return false;
        return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
    }
    bool isSame(TreeNode* root1,TreeNode* root2){
        if(root1==nullptr && root2==nullptr)return true;
        else if(root1==nullptr || root2==nullptr)return false;
        else if(root1->val==root2->val){
            return isSame(root1->left,root2->left) && isSame(root1->right,root2->right);
        }
        return false;
    }
};
1110、删除成林

给了一棵树,和需要删除的节点的数字的列表,删除树中这一些节点,会生成一片森林,返回它。

思路:注意到某一个节点为root的子树中,可能还有其他节点需要删除,因此考虑先处理它的子节点再处理当前节点,考虑采用后序便利的方式。


class Solution {
    
public:
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        //后续遍历所有节点,如果出现了就删除该节点,并且返回一个null值给上一层
        sort(to_delete.begin(),to_delete.end());
        vector<TreeNode*> ans;
        TreeNode* ret = delNodes_postSeq(root,to_delete,ans);
        if(ret!=nullptr)ans.push_back(ret);
        
        return ans;
    }

    TreeNode* delNodes_postSeq(TreeNode* root,vector<int>& to_delete,vector<TreeNode*>& ans){
        if(root==nullptr)return nullptr;
        root->left = delNodes_postSeq(root->left,to_delete,ans);
        root->right = delNodes_postSeq(root->right,to_delete,ans);
        //便利里面所有药材删除的纸
        for(int cur:to_delete){
            if(cur==root->val){
                //需要删除
                cout<<"需要删除"<<endl;
                if(root->left!=nullptr){
                    ans.push_back(root->left);
                }
                if(root->right!=nullptr){
                    ans.push_back(root->right);
                }
                // root->left = nullptr;
                // root->right = nullptr;
                return nullptr;
            }
            if(cur>root->val)break;
        }
        return root;
        
    }
};
  • 注意:这里面的vector可以转换为unordered_set,这样判断里面是否有当前节点的值就变方便了,可以使用
    set_A.count(root->val)来计数,里面有多少个该值。
    初始化时 unordered_set dict(to_delete.begin(), to_delete.end()); 使用整个vector初始化。
404、左叶子结点之和

给了一棵树,求他的所有左叶子节点的和。

思路:最好是每个左叶子节点自己返回值,因此就需要传递一个参数,表示当前节点是否是左叶子。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        //给出以该节点为根的所有左叶子节点的和
        if(root==nullptr)return 0;
        return getSum(root,false);
    }
    int getSum(TreeNode* root,bool isLeft){
        if(root==nullptr)return 0;
        if(root->left==nullptr&&root->right==nullptr){
            if(isLeft==true)return root->val;
            else return 0;
        }else{
            return getSum(root->left,true)+getSum(root->right,false);
        }
    }
};
236、二叉树的最近公共祖先

给了一个二叉树和其中的两个节点值,求他们的深度最大的公共祖先。
思路:考虑到两个节点可能在一个节点的两侧,那么这个节点就是他们的公共祖先,否则可能某一个节点是他们共同的最近公共祖先。那么就看这两个节点分别在哪一些枝上出现。

如果某个值在某个子树上出现了,那么就返回最可能的一个树节点的值。如果在一个节点的两侧都有返回值,说明两个节点分布在两侧,当前节点是要返回的值。
如果找到一个值相等的节点,就不必继续遍历下去了,因为当前节点已经是一个可能的root了。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //重点:如果该子树存在p/q中的一个,则需要有返回值,根据返回值来判断p、q的位置
        if(root==nullptr)return nullptr;
        if(root->val==p->val || root->val==q->val)return root;
        TreeNode* l = lowestCommonAncestor(root->left,p,q);
        TreeNode* r = lowestCommonAncestor(root->right,p,q);
        if(l==nullptr && r==nullptr)return nullptr;
        else if(l==nullptr)return r;
        else if(r==nullptr)return l;
        return root;
    }
};
109、有序链表转换为二叉搜索树

给了一个有序的链表,把它转换为一个平衡的二叉搜索树。

方法:构建树,首先考虑找到它的根节点。那么对于一个平衡的有序链表来说,根节点应该就是中间的那个点位置,才能使得两边高度不会偏差。

class Solution {
    vector<int> vec;
public:
    TreeNode* sortedListToBST(ListNode* head) {
        //获取里面全部的值
        while(head!=nullptr){
            vec.push_back(head->val);
            head = head->next;
        }
        return get(0,vec.size()-1);
    }
    TreeNode* get(int l_n,int r_n){
        TreeNode* root = new TreeNode();
        if(l_n>r_n)return nullptr;
        long long mid_n = 0;
        mid_n = (r_n+l_n+1) >> 1;
        root->val = vec[mid_n];
        root->left = get(l_n,mid_n-1);
        root->right = get(mid_n+1,r_n);
        return root;
    }
};

注意:
1、其中可以优化:通过把要传递的同一个值放到外围可以大大减小时间复杂度和空间复杂度。
2、对于求中间值:可以通过(l+r)>> 1右移一位的方式来实现。
但是这样的复杂度还是有点高,还可以简化。

897、递增顺序搜索树

给了一个二叉搜索树,使用它构建一个每个节点只有右节点的二叉搜索树。

思路:边搜索边构建
维护一个pre_n节点,它是一个构建的新二叉树的头节点的前一个节点,方便二叉树的同一生成。再使用一个pre节点,记录当前构建到的那个节点,每次只用在他的右边新加点就可以了。

class Solution {
TreeNode* pre_h;
TreeNode* pre;
public:
    TreeNode* increasingBST(TreeNode* root) {
        if(root==nullptr)return pre;
        pre_h = pre;
        pre = new TreeNode();
        //中序遍历二叉搜索树
        pre_h = pre;
        inorder(root);
        return pre_h->right;

    }

    void inorder(TreeNode* root){
        if(root==nullptr)return;
        inorder(root->left);
        TreeNode* cur = new TreeNode();
        cur->val = root->val;
        pre->right = cur;
        pre = cur;
        inorder(root->right);
        return;
    }
};

广度优先:层次遍历

使用广度优先的方法进行层次遍历

637、二叉树的层平均值

给定一个二叉树,求它每层节点值的平均值数组。

思路:使用层次遍历,遍历时记录每一层的个数与值。

 */
class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        //返回每层节点的平均值
        //使用一个队列
        queue<TreeNode*> Node_queue;
        Node_queue.push(root);
        vector<double> ret;
        while(Node_queue.size()!=0){
            int n = Node_queue.size();
            long long val = 0;
            int cnt = 0;
            for(int i =0;i<n;i++){
                TreeNode* tmp = Node_queue.front();
                val += tmp->val;
                cnt++;
                Node_queue.pop();
                if(tmp->left != nullptr)Node_queue.push(tmp->left);
                if(tmp->right != nullptr)Node_queue.push(tmp->right);
            }
            if(cnt!=0)ret.push_back((double)val/cnt);
        }
        return ret;

    }
};

注意:避坑
1、使用int值可能会越界,因此可以使用double/long long ,他们都是32位。

513、找树左下角的值

给了一个二叉树,找到它最下面一层的最左边的值。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        //层次遍历
        //由于在一层的开始时,无法确定该层是否为最后一层,因此需要每一层便利时都记录该层第一个节点
        if(root==nullptr)return 0;
        queue<TreeNode*> q ;
        q.push(root);
        int l=0;
        while(q.size()!=0){
            int n = q.size();
            for(int i =0;i<n;i++){
                TreeNode* cur = q.front();
                if(i==0)l = cur->val;
                q.pop();
                if(cur->left!=nullptr)q.push(cur->left);
                if(cur->right!=nullptr)q.push(cur->right);
            }
        }
        return l;

    }
};
105、从前序遍历和中序遍历建立树

给了一个一个树的前序遍历和中序遍历的结果,需要重新构造这棵树。

思考:注意到根据前序和中序每次可以确定根结点和左右子树的前序和中序的序列,再对左右子树分别递归。

class Solution {
    unordered_map<int,int> map;
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        for(int i =0;i<preorder.size();i++){
            map[inorder[i]] = i;
        }
        TreeNode* root = buildTree_my(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
        return root;
    }
    TreeNode* buildTree_my(vector<int>& preorder,vector<int>& inorder,int pre_left,int pre_right,int in_left,int in_right){
        if(pre_left>pre_right || in_left>in_right)return nullptr;
        //获取根节点
        TreeNode* root = new TreeNode(preorder[pre_left]);
        //获取位置
        //获取其左右子树的大小
        int p = map[preorder[pre_left]];
        int left_size = p-in_left;
        //左子树
        root->left = buildTree_my(preorder,inorder,pre_left+1,pre_left+left_size,in_left,p-1);
        //右子树
        root->right =  buildTree_my(preorder,inorder,pre_left+1+left_size,pre_right,p+1,in_right);
        return root;
    }
};

需要注意的是map的使用方式,还有需要注意下标的值需要好好考虑。

注意:⚠️如果把map放在参数里传递,会提高复杂度,最后两个测试用例会超时,因此可以考虑把map放在全局变量里。

889、从前序和后续建立树的结构

注意到前序的第一个值为root,对应后序的最后一个元素。第二个值为左子树的前序的首值,也就是左子树的头节点。
每次从中取出这个节点,找到左子树的位置,然后再找到左子树的大小,继续使用递归构建。

class Solution {
map<int,int> m;
public:
    TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
        //使用一个map,快速找到它在后序中对应的位置。
        for(int i = 0 ;i<postorder.size();i++){
            m[postorder[i]]= i;
        }
        return construct(preorder,postorder,0,postorder.size()-1,0,postorder.size()-1);
    }
    TreeNode* construct(vector<int>& preorder,vector<int>& postorder,int pre_left,int pre_right,int post_left,int post_right){
        //根据当前的
        if(pre_left>pre_right)return nullptr;
        TreeNode* root = new TreeNode();
        //获取root节点
        root->val = preorder[pre_left];
        if(pre_left==pre_right)return root;
        int pos = m.at(preorder[pre_left+1]);
        //由于随机返回一个树,那就取一个左节点为null的
        root->left = construct(preorder,postorder,pre_left+1,pre_left+1+pos-post_left,post_left,pos);
        root->right = construct(preorder,postorder,pre_left+2+pos-post_left,pre_right,pos+1,post_right-1);
        return root;
    }
};
106、从中序和后续来构建二叉树

同上

class Solution {
map<int,int> m;
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        for(int i =0;i<inorder.size();i++){
            m[inorder[i]] = i;
        }
        return bT(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
    }
    TreeNode* bT(vector<int>& inorder, vector<int>& postorder,int in_left,int in_right,int post_left,int post_right){
        if(post_right<post_left)return nullptr;
        TreeNode* root = new TreeNode();
        root->val = postorder[post_right];
        int pos = m[root->val];
        root->left = bT(inorder,postorder,in_left,pos-1,post_left,post_left+pos-1-in_left);
        root->right = bT(inorder,postorder,pos+1,in_right,post_left+pos-in_left,post_right-1);
        return root;
    }
};
144、前序遍历

给了一个树的根节点,求它的前序遍历返回的value数组。对于空节点不需要加入值。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        //使用递归的方式
        vector<int> ret;
        preorder_my(root,ret);
        return ret;
    }

    void preorder_my(TreeNode* root,vector<int>& ret){
        if(root==nullptr){
            //空节点不需要添加值进入
            return;
        }
        ret.push_back(root->val);
        preorder_my(root->left,ret);
        preorder_my(root->right,ret);
        return;
    }
};

迭代方法实现:借助stack来实现

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        //使用迭代的方式
        //使用栈来模拟前序遍历
        vector<int> ret;
        if(root==nullptr)return ret;
        stack<TreeNode*> s;
        s.push(root);
        while(!s.empty()){
            TreeNode* cur = s.top();
            s.pop();
            ret.push_back(cur->val);
            if(cur->right!=nullptr)s.push(cur->right);
            if(cur->left!=nullptr)s.push(cur->left);
        }
        return ret;
    }

注意:需要使用栈来模拟前序遍历的结果。
由于栈是先进后出,按照前序遍历“根左右”的顺序,应该右节点先入,再左节点入。

94、二叉树的中序遍历
class Solution {
vector<int> vec;
public:
    vector<int> inorderTraversal(TreeNode* root) {
        inorder(root);
        return vec;
    }
    void inorder(TreeNode* root){
        if(root==nullptr)return;
        inorder(root->left);
        vec.push_back(root->val);
        inorder(root->right);
        return;
    }
};

借助栈来实现,由于直接使用栈需要写两层循环,太复杂了。考虑一个更直观的方法,由于中序遍历,每次对于一个头节点需要依次入栈右-根-左,就需要将根结点再次入栈,第二次遇到的时候只记录值,而不需要再入栈,因此借助一个pair使得每次入栈的为一个二元组。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        //使用栈实现
        //使用颜色标记法实现,其实就是记录节点是否被便利过的两种状态,0表示还需要加入左右节点,1表示直接使用自己就好
        vector<int> ret;
        if(root==nullptr)return ret;
        //否则进行中序遍历
        stack<pair<int,TreeNode*>> st;
        st.push(make_pair(0,root));
        while(!st.empty()){
            auto [color,root] = st.top();
            st.pop();
            if(color==1){
                ret.push_back(root->val);
            }else{
                if(root->right!=nullptr)st.push(make_pair(0,root->right));
                st.push(make_pair(1,root));
                if(root->left!=nullptr)st.push(make_pair(0,root->left));
            }
        }
        return ret;
    }
};
146、二叉树的后续遍历

给了一个二叉树,求其后续遍历的结果

思路:按照颜色标记法,记录哪些还需要将其和其左右入栈。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        //使用迭代
        vector<int> ret;
        stack<pair<int,TreeNode*>> st;
        if(root==nullptr)return ret;
        st.push(make_pair(0,root));
        while(!st.empty()){
            auto [color,rt]= st.top();
            st.pop();
            if(color==1){
                ret.push_back(rt->val);
            }else{
                st.push(make_pair(1,rt));
                if(rt->right!=nullptr)st.push(make_pair(0,rt->right));
                if(rt->left!=nullptr)st.push(make_pair(0,rt->left));
            }
        }
        return ret;
    }
};

二叉查找树

二叉查找树(Binary Search Tree, BST)是一种特殊的二叉树:对于每个父节点,其左子节点的值小于等于父结点的值,其右子节点的值大于等于父结点的值。同时因为二叉查找树是有序的,对其中序遍历的结果即为排好序的数组。

如果二叉排序树是平衡的,则n个节点的二叉排序树的高度为Log2(n+1),其查找效率为O(Log2n),近似于折半查找。如果二叉排序树完全不平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。一般的,二叉排序树的查找性能在O(Log2n)到O(n)之间。

99、回复二叉搜索树

给了一个二叉搜索树,其中有两个节点被调换了位置。求把他换回来后的树。

思路:二叉搜索树的中序遍历的结果是一个升序数组,那么将两个节点对换意味着升序数组中有两个元素被对换了,因此每次往后着,如果出现了一个后面的数字比它前面一个数字小,说明这两个数字有问题,第一次出现这样的情况,说明是应该是那个大的数往前移动了,第二次出现这样的情况是小的数往后移动。那么如果是只出现一次这样的情况,说明是连着的两个数字交换了。

class Solution {
    TreeNode* fir = nullptr;
    TreeNode* sec = nullptr;
    int cnt = 0;
public:
    void recoverTree(TreeNode* root) {
        //当某一个节点和其相连节点不符时,那一定是该节点/关联节点y
        //二叉搜索树,考虑中序遍历,正常情况下应该是一个升序的数组
        //注意到一共只有两个节点错序,那么就应该是数组中的某两个值被交换,一个大值被交换到前面,一个小值在后面
        //记录一个它的前序节点,每次纪录比较前序节点的值是否比当前值大。
        TreeNode* pre = nullptr;
        in_oreder(root,pre);
        if(fir!=nullptr && sec!=nullptr){
            int mid = fir->val;
            fir->val = sec->val;
            sec->val = mid;
        }
        return;
    }

    void in_oreder(TreeNode* root,TreeNode*& pre){
        //中序遍历找到两个不合适的值
        if(root==nullptr)return;
        
        in_oreder(root->left,pre);
        //处理当前值
        // cout<<"cur = "<<root->val<<endl;
        if(pre!=nullptr)cout<<"pre1 = "<<pre->val<<endl;
        if(pre!=nullptr&&pre->val>root->val){
            if(cnt==0){
                // cout<<"cnt==0"<<endl;
                fir = pre;
                sec = root;
            }else{
                // cout<<"cnt!=0"<<endl;
                sec = root;
            }
            cnt++;
        }
        pre = root;
        // if(pre==nullptr)cout<<"pre = nul"<<endl;
        // else cout<<"pre2 = "<<pre->val<<endl;

        in_oreder(root->right,pre);   
    }
};
  • 踩坑:注意因为没有传&引用类型而导致pre出错。
  • ‘*’ 与 ‘&’不相等。
669、修剪二叉搜索树

给了一个二叉搜索树和一个范围,需要删除掉不符合范围内的数

解体思路:需要充分考虑到二叉搜索树的性质,左子树上的元素都比当前值小,右子树元素都比当前值大。
如果一个根结点的值不满足范围,可能会比范围大,或者比范围小。这样就一定去除了它的一个子树。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(root==nullptr)return nullptr;
        if(root->val>high){
            //root右边的全部都大了
            root = trimBST(root->left,low,high);
        }else if(root->val<low){
            //root左边的全部都大了
            root = trimBST(root->right,low,high);
        }else{
            //root保留
            root->left = trimBST(root->left,low,high);
            root->right = trimBST(root->right,low,high);
        }
        return root;
    }
};
538、二叉查找树转换为累加树

给了一个二叉查找树,求它转换而成的累加树

思路:先遍历右节点,再遍历中间,再遍历左。

class Solution {
public:
    TreeNode* convertBST(TreeNode* root) {
        //其实就是先遍历右节点,再遍历中间,再便利左。并且把值一直累加
        int c= 0;
        invert(root,c);
        return root;
    }
    void invert(TreeNode* root,int& val){
        if(root==nullptr)return;
        invert(root->right,val);
        root->val += val;
        val = root->val;
        invert(root->left,val);
        return;
    }
};
235、二叉查找树的最近祖先

给了一个二叉搜索树,和其中的两个节点,求深度最深的他们的公共祖先。

思路:如果两个节点产生了分歧,那么必然在一个节点上开始分道扬镳,值的大小应该为 p root q/q root p。
那么每到一个位置去判断p root q三者的大小关系。就可以进行判断。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)return root;
        int v = root->val;
        if((root->val-p->val)*(root->val-q->val)<=0)return root;
        else if((root->val-p->val)>0){
            return lowestCommonAncestor(root->left,p,q);
        }else{
            return lowestCommonAncestor(root->right,p,q);
        }
    }
};

注意:如果有一个值等于当前遍历到的节点,那也需要返回

530、二叉查找树找到两个节点的最小差值

给了一个二叉查找树,求两个节点差的最小值。

思路:二叉查找树的中序遍历为递增数组,就可以直接两两比较。

class Solution {
int minDiff = INT_MAX;
public:
    int getMinimumDifference(TreeNode* root) {
        //中序遍历为递增,等于在递增数列上只用记录相邻节点的差的最小值
        int pre = -1;
        getMin(root,pre);
        return minDiff;

    }
    void getMin(TreeNode* root,int& pre){
        if(root==nullptr)return;
        //中序遍历
        getMin(root->left,pre);
        if(pre!=-1){
            minDiff = min(abs(pre-root->val),minDiff);
        }
        pre = root->val;
        getMin(root->right,pre);
        return;
    }
};

字典树Trie

用于判断某个字符串是否存在||是否有某种字符前缀
在这里插入图片描述
(来自leetcode101)
可以在O(n)内完成对长度为n的单词的搜索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值