C++学习笔记-二叉树(4)

资料来源:代码随想录

21.合并二叉树 617

有两种迭代法:一种是直接修改二叉树1,把2合并到1上去,合并之后的1就是要求的二叉树;另一种是重新定义一棵二叉树。

直接修改二叉树1:内存消耗31.4M,用时32ms

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        //函数的参数及返回值:传入要合并的两棵二叉树的根节点,返回合并后的二叉树的根节点

        //终止条件:以下两种情况已经包含了1和2都为空的情况
        if(root1==NULL) return root2;  //如果1为空,合并后的结果应该为2,所以直接返回2
        if(root2==NULL) return root1;  //如果2为空,合并后的结果应该为1,所以直接返回1

        //单层递归逻辑:把2合并到1上
        root1->val=root1->val+root2->val;  //中

        //递归
        root1->left=mergeTrees(root1->left,root2->left); //t1 的左子树是:合并 t1左子树 t2左子树之后的左子树
        root1->right=mergeTrees(root1->right,root2->right);  //t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树

        //最终t1就是合并之后的根节点
        return root1;
    }
};

重新定义二叉树:内存消耗32.1M,用时16ms

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        //函数的参数及返回值:传入要合并的两棵二叉树的根节点,返回合并后的二叉树的根节点

        //终止条件:以下两种情况已经包含了1和2都为空的情况
        if(root1==NULL) return root2;  //如果1为空,合并后的结果应该为2,所以直接返回2
        if(root2==NULL) return root1;  //如果2为空,合并后的结果应该为1,所以直接返回1

        //单层递归逻辑
        TreeNode* root=new TreeNode(0);
        root->val=root1->val+root2->val;  //中

        //递归
        root->left=mergeTrees(root1->left,root2->left); 
        root->right=mergeTrees(root1->right,root2->right);  

        return root;
    }
};

22.二叉搜索树中的搜索 700

二叉搜索树是一个有序树:

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

这就决定了,二叉搜索树,递归遍历和迭代遍历和普通二叉树都不一样。

二叉搜索树的特性帮助我们决定了递归时的遍历顺序。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        //递归函数的参数和返回值:传入要搜索的二叉树的根节点和要搜索的值,返回搜到的节点

        //终止条件:根节点为空则节点不存在,返回空;根节点的值等于要搜索的值,说明找到了,返回根节点
        if(root==NULL || root->val==val) return root; 

        //单层递归,注意这里的递归函数有返回值,需要用一个变量去接
        TreeNode* result=NULL;
        if(val<root->val) result=searchBST(root->left,val); //要搜索的值小于根节点的值,说明在左子树,就向左去搜索
        if(val>root->val) result=searchBST(root->right,val);  //要搜索的值大于根节点的值,说明在右子树,则向右搜索

        return result;
    }
};

23.验证二叉搜索树 98

利用二叉搜索树的特性,采用中序遍历,这样遍历得到的元素是递增的,可以用这一点来判断是否是二叉搜索树。

方法一:用一个数组来记录遍历得到的所有数据,判断这个数组是否是递增的。

class Solution {
private:  //先写一个中序遍历的递归算法,得到二叉树中的所有数据
    //因为是直接把数据放进一个全局变量的数组里,所以不需要返回值,传入的参数为二叉树的根节点
    vector<int> vec;
    void traversal(TreeNode* root)
    {
        if(root==NULL) return;
        traversal(root->left);   //中序遍历
        vec.push_back(root->val);
        traversal(root->right);
    }


public:
    bool isValidBST(TreeNode* root) {
        vec.clear();
        traversal(root);
        for(int i=1; i<vec.size(); i++)
        {
            if(vec[i]<=vec[i-1])  return false;  //后一个值小于等于前一个值,说明数组内不是递增的,则不是二叉搜索树
        }
        return true;
    }
};

方法二:设置一个最大值,每次更新最大值为节点的值,相当于最大值记录的一直是当前遍历节点前一个节点的值。那么一旦出现当前节点的值小于等于最大值,说明不是递增的,则不是二叉搜索树。

注意最大值初始值的设置,本题的测试用例里有int的最小值,所以如果初始最大值设置为int的最小值的话,可能在中间就错误返回了。所以把最大值设置为long long的最小值。

class Solution {
public:
    long long maxVal=LONG_MIN;
    bool isValidBST(TreeNode* root) {
        if(root==NULL) return true;  //空二叉树也是二叉搜索树

        //左:
        bool left=isValidBST(root->left);

        //中:处理逻辑
        if(maxVal>=root->val) return false;  //前一个节点的值大于等于当前节点的值
        else 
        {
            maxVal=root->val; //否则更新最大值
        }

        //右:
        bool right=isValidBST(root->right); 

        return left && right;  //看起来只判断了两边,但实际上在“左中右”这个过程中就把根节点已经包含进去了

    }
};

方法三:那如果测试用例里还包含long long最小值呢?那就不设置最大值了,直接用前后两个节点进行比较

class Solution {
public:
    TreeNode* pre=NULL;  //定义前一个节点
    bool isValidBST(TreeNode* root) {
       if(root==NULL) return true;

       bool left=isValidBST(root->left);

        //中:
       if(pre!=NULL && pre->val>=root->val) return false;  //前一个节点的值大于等于当前节点的值
       else {
           pre=root; //前一个节点的指针向后移动
       }

       bool right=isValidBST(root->right);

       return left && right;
    } 
};

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

有的题目里,需要判断二叉树是否符合某一特性,或者要搜索特定的某一条路径或某一节点,那么要在搜索到了之后立刻返回,就需要返回值。本题是要遍历整棵二叉树,而结果值存进一个全局变量里就可以,所以不需要返回值

class Solution {
private:
    //先定义两个全局变量
    int result=INT_MAX;
    TreeNode* pre=NULL;
    void traversal(TreeNode* cur)
    {
        if(cur==NULL) return;  
        
        traversal(cur->left);  //递归的过程中是有回溯的,遍历左节点之后要回溯才能退回到中间节点

        //中的处理逻辑
        if(pre!=NULL)
        {
            result=min(result,cur->val-pre->val);  //保证result始终是最小的差值   
        }
        pre=cur;  //cur在递归的过程中一步一步移动,pre跟着它

        traversal(cur->right);
    }

public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return result;
    }
};

34.二叉搜索树中的众数 501

如果没有说是二叉搜索树的话,可以遍历整棵树,寻找出现频率最高的即可。

步骤:

1.遍历二叉树,用map来记录元素的值和各个值出现的频率,因为是要遍历整棵二叉树,所以任何一种遍历方式都可以;

2.定义一个排序函数;

3.map里无法针对value直接排序,只能针对key排序(unordered的话key也不可以),所以需要把map放进数组里面进行排序;

4.排好序后,取出现频率最高的元素放进result数组里。

class Solution {
private:
    //递归遍历整棵二叉树
    void traversal(TreeNode* cur, unordered_map<int,int>& map)  //用map来存放遍历结果,key存放数值,value存放出现的次数
    {
        if(cur==NULL) return;
        //采用前序遍历
        map[cur->val]++;  //中:统计出现次数
        traversal(cur->left,map);
        traversal(cur->right,map);
        return;
    }

    //定义一个在数组中排序的函数,这里没有懂为什么传入的是静态变量
    bool static compare(const pair<int,int>& a, const pair<int,int>& b)
    {
        return a.second>b.second;   //按照pair里value的值进行降序排列
    }

public:
    vector<int> findMode(TreeNode* root) {
        unordered_map<int,int> map;  //定义一个map用于存放遍历结果
        vector<int> result;  //存放最后的数值结果
        if(root==NULL) return result;
        traversal(root,map);
        vector<pair<int,int>> vec(map.begin(),map.end());  //把map里的数据放进数组里以便排序,数据形式还是pair
        sort(vec.begin(),vec.end(),compare);  //按照出现的次数进行降序排列
        result.push_back(vec[0].first);  //排好序后,vec数组里第一个元素就是出现频率最高的元素,把它存进结果数组里
        for(int i=1; i<vec.size(); i++)  //因为允许有相同元素,所以再找一下还有没有出现频率和最高频率相同的,也要存起来
        {
            if(vec[i].second==vec[0].second) result.push_back(vec[i].first);
        }
        return result;
    }
};

如果确定是二叉搜索树,就可以利用二叉搜索树遍历的性质,即中序遍历是有序的。具体方法是使用两个指针,比较前后元素是否相同(根据题目中给出的性质,相同的元素一定是相邻的),如果相同,就count+1,不同的话count=1。

class Solution {
private:
    int maxCount=0;  //记录最大频率
    int count=0;  //记录频率
    TreeNode* pre=NULL;  //定义前一个指针
    vector<int> result;  //存放结果

    void traversal(TreeNode* cur)
    {
        if(cur==NULL) return;

        traversal(cur->left);  //左

        //中:处理逻辑
        if(pre==NULL)  //第一个节点
        {
            count=1;
        }
        else if(pre->val==cur->val)  //当前节点的值与前一个节点的值相等
        {
            count++;
        }
        else
        {
            count=1;   //不相等的话,该元素值出现次数重新等于1
        }
        pre=cur;  //更新节点

        if(count==maxCount)
        {
            result.push_back(cur->val);   //碰到最大次数的话,把值存起来
        }

        if(count>maxCount)  //出现次数大于最大次数的话
        {
            maxCount=count;  //更新最大次数
            result.clear();  //result里存放的值已经不是出现频率最高的了,已经失效,要清空
            result.push_back(cur->val);  //存入新的
        }

        traversal(cur->right);  //右

        return;
    }

public:
    vector<int> findMode(TreeNode* root) {
        count=0;
        maxCount=0;
        TreeNode* pre=NULL;   //这三行不要也可以,因为全局变量已经在前面定义了,但加上会快一点
        
        result.clear();

        traversal(root);
        return result;
    }
};

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

需要自底向上查找,如何实现?利用回溯。

后序遍历里包含天然的回溯过程:将左右子树处理后的结果返回给中间节点

情况1:如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。注意题目要求二叉树中的元素没有重复,所以不会出现两个p或q。

情况2:节点本身p(q),它拥有一个子孙节点q(p)。 

实现情况1的过程包含了情况2,因为“遇到p或q就返回”,也包含了p或q本身就是公共祖先的情况。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //返回值和传入的参数:要找最近公共祖先节点,所以返回treenode类型;要传入的是给定二叉树的根节点和两个指定节点

        //终止条件
        //1.空节点
        //2.对应情况二:p或q就是根节点,那它本身就是一个公共祖先,则可以直接返回root
        if(root==NULL) return NULL;
        if(root==q || root==p) return root;

        //情况一:
        //递归:后序
        TreeNode* left=lowestCommonAncestor(root->left,p,q);  //左子树进行递归,返回在左子树找到的结果
        TreeNode* right=lowestCommonAncestor(root->right,p,q);  //右子树进行递归,返回右子树找到的结果
        //中:处理逻辑
        if(left!=NULL && right!=NULL)  
        {
            return root; //左子树和右子树的返回结果均不为空,说明分别找到了p和q,那么最近公共祖先就应该是root
        }
        else if(left==NULL && right!=NULL)
        {
            return right;  //左子树返回结果为空而右子树不为空,说明右子树里有符合要求的,那么最近公共祖先就应该是right,则返回right
        }
        else if(left!=NULL && right==NULL)
        {
            return left;  //左子树返回结果不为空,说明左子树里有符合要求的
        }
        else  //左右返回结果均为空
        {
            return NULL;
        }
    }
};

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

本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。

在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?

因为是有序树,所以如果中间节点是 q 和 p 的公共祖先,那么中节点的数一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p

那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q 和 p的公共祖先。 那问题来了,一定是最近公共祖先吗?当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间中,那么cur就是 p和q的最近公共祖先

所以思路:比较cur->val和p->val、q->val的大小关系。通过判断当前节点是否在区间中,来判断出当前节点是否是最近公共祖先。

class Solution {
private:
    //先写一个递归函数
    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q)
    {
        //终止条件:遇到空则返回
        if(cur==NULL) return NULL;

        //单层递归逻辑:遍历二叉搜索树,寻找[p,q]或[q,p],注意是左闭右闭
        //若cur->val比p、q都大,说明cur太靠右了,目标区间在左子树上,需要向左边递归
        //如果比P和Q都小,说明cur太靠左了,目标区间在右子树上,需要向右递归
        //现在是不知道p和q谁大谁小的,但是除了都大或者都小这两种情况之外,cur一定会在p和q的区间[p,q]或[q,p]里,不论谁大谁小
       
        if(cur->val>p->val && cur->val>q->val)  //不知道p q谁大谁小,所以两个都要判断
        {
            TreeNode* left=traversal(cur->left,p,q);
            if(left!=NULL)  //返回的left不为空,说明找到了
            {
                return left;
            }
        }

        if(cur->val<p->val && cur->val<q->val)
        {
            TreeNode* right=traversal(cur->right,p,q);
            if(right!=NULL)
            {
                return right;  
            }
        }

        //当cur在p和q的区间[p,q]或[q,p]里,cur本身就是最近公共祖先
        return cur;
    }

public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root,p,q);
    }
};

这里有一个“搜索边”还是“搜索整棵树”的区别。

如果是搜索边的话,当递归函数返回值符合条件,就会立即返回,比如本题中left!=NULL,就直接返回left;

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

如果是搜索整棵树,就会把搜索到的节点先存起来,后续可能会有处理逻辑。

left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;

29.二叉搜索树中的插入操作 701

虽然说有很多种插入方式,但我们只把新节点插到叶子节点就行。所以关键点在于,如何在遍历到叶子节点之后,在新节点上建立起父子节点关系:通过递归函数返回值完成了新加入节点的父子关系赋值操作,下一层将加入节点返回,本层用root->left或者root->right将其接住

同样利用二叉搜索树的有序性质。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        //终止条件:
        if(root==NULL)  //如果节点为空,说明遍历到叶子节点了,就可以用val建立新节点了
        {
            TreeNode* node= new TreeNode(val);
            return node;  //把新建立的节点返回上一层
        }

        //单层递归逻辑
        if(root->val > val) //说明应该把新节点插入左子树
        {
            root->left=insertIntoBST(root->left,val);  //用root->left接住下一层返回的要插入的新节点,就在root和新节点之间建立了父子关系
        }
        if(root->val < val)  //应该把新节点插入右子树
        {
            root->right=insertIntoBST(root->right,val);
        }
        return root;
    }
};

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

采用递归法。

终止条件:找到要删除的节点就可以终止了,那么还有删除节点的操作,所以删除节点也在终止条件里。

有五种情况:

没遍历到要删除的节点:

1.返回空;

遍历到要删除的节点:

2.要删除的节点是叶子节点,左右孩子均为空,直接删除即可,然后返回NULL给上一层,因为要删除的本身就是叶子节点,删了之后上一层就变成新的叶子节点,所以用root->left或root->right接收NULL是正确的;

3.要删除的节点左孩子为空,右孩子不为空,则右孩子补位,返回右孩子给上一层;

4.要删除的节点左孩子不为空,右孩子为空,则左孩子补位,返回左孩子给上一层;

5.要删除的节点左右孩子均不为空,可以把左子树并到右子树上,也可以把右子树并到左子树上,是一样的,选一种即可。如果选把左子树并到右子树上:考虑二叉搜索树的性质,左子树的所有节点值必然小于右子树,所以要在右子树上找比根节点数值大的最小值,把左子树挂在这个节点的左孩子,也就是把左子树放在右子树的最左下节点的左孩子位置。然后返回右子树的根节点来补位。

2345返回上一层的都是删除节点之后补位的新节点

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        //终止条件:
        //1.没找到要删除的节点,则返回空
        if(root==NULL) return NULL;

        //找到要删除的节点了
        if(root->val==key)
        {
            if(root->left==NULL && root->right==NULL)  //2.要删除的节点是叶子节点,直接删除即可,然后返回空给上一层,因为这个删除之后上一层会是新的叶子节点,满足左右均为空
            {
                delete root;  //释放内存
                return NULL;
            }
            else if(root->left!=NULL && root->right==NULL)  //3.要删除的节点左孩子不为空右孩子为空,则需要左孩子补位,所以把左孩子返回给上一层
            {
                TreeNode* node=root->left;
                delete root;
                return node;
            }
            else if(root->left==NULL && root->right!=NULL)  //4.要删除的节点左孩子为空右孩子不为空,则需要右孩子补位,把右孩子返回给上一层
            {
                TreeNode* node=root->right;
                delete root;
                return node;
            }
            else  //5.左右孩子均不为空,则把左子树并到右子树去
            {
                TreeNode* cur=root->right; 
                while(cur->left!=NULL)
                {
                    cur=cur->left;   //找右子树的最左节点
                }
                cur->left=root->left;  //把左子树赋给右子树最左节点的左孩子,然后就可以删除原来的节点了
                TreeNode* tmp=root;
                root=root->right;  //右子树的根节点此时成为新的根节点
                delete tmp;  //释放内存
                return root;
            }
        }

        //递归:开始接收下一层返回的节点
        if(root->val > key)  root->left=deleteNode(root->left,key);  //要删除的节点在左子树,所以用左孩子去接收下面删除节点之后返回来的补位的新节点
        if(root->val < key)  root->right=deleteNode(root->right,key);  //要删除的节点在右子树,所以用右孩子去接收
        return root;

    }
};

31.修建二叉搜索树 669

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        //终止条件:
        if(root==nullptr) return nullptr;

        //以下两个if用来处理当根节点也不在区间内的情况
        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;  //返回处理完的新二叉树的根节点

    }
};

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

有不同的构造方法。注意区间是左闭右闭。

class Solution {
private:
    //先用一个递归函数来构建二叉树
    TreeNode* traversal(vector<int>& nums, int left, int right) //要返回构建的二叉树的根节点;传入的是数组和数组的左右两个下标
    {
        //终止条件:递归到left>right就说明数组元素已经完了
        if(left>right) return nullptr;

        //mid=(left+right)/2   这种方法也可以,但容易因为数据过大而越界,本题不会越界,但要注意这个可能
        int mid=left+((right-left)/2);  //计算中间值,这种方法意味着如果数组元素有偶数个的话,选择最中间两个左边的那个作为中间值,就是较小的那个作为中间值
        TreeNode* root=new TreeNode(nums[mid]);  //用中间值构建根节点

        root->left=traversal(nums,left,mid-1); //用区间[left,mid-1]来构建左子树,并把构建好的左子树的根节点返回给root->left
        root->right=traversal(nums,mid+1,right);  //用区间[mid+1,right]来构建右子树
        //注意上面这两个括号里面必须是left而不能是0,必须是right而不能是size-1,因为在递归过程中需要用到的数组是在不断变化的
        return root;  //都构建好后,返回根节点
    }

public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode* root= traversal(nums,0,nums.size()-1);   //调用递归函数来构建二叉树,要传入两个下标以提供初始数组
        return root;
    }
};

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

因为累加是从大到小累加起来,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了

class Solution {
private:
    int pre=0;  //定义一个变量,用来存放当前节点的再前一个节点的数值
    void traversal(TreeNode* cur)  //因为不需要返回什么,只是要遍历整棵二叉树并更新节点值,所以返回值类型为void
    {
        //终止条件:
        if(cur==NULL) return;
        traversal(cur->right);  
        //中:
        cur->val=cur->val+pre;
        pre=cur->val;  //这样pre才能保证始终是当前节点的前一个节点值
        traversal(cur->left);
    }

public:
    TreeNode* convertBST(TreeNode* root) {
        pre=0;
        traversal(root);  //把需要遍历更新的二叉树的根节点传入递归函数
        return root;  //返回递归结束后新二叉树的根节点
    }
};

二叉树部分,完结撒花~~~~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值