代码随想录:二叉树篇

二叉树

98.验证二叉树

可以用递归,或者迭代中序遍历。通过预先定义一个pre节点,使其充当前一个节点,让其与当前cur节点进行比较,pre节点值大于等于当前cur节点的val,即return false。

	//迭代法
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur=root;
        while(cur!=NULL || !st.empty()){
            if(cur != NULL){
                st.push(cur);
                cur=cur->left;
             
            }
            else{
                cur=st.top();
                st.pop();
                if(pre!=NULL && pre->val>=cur->val) return false;
                pre=cur;
                cur=cur->right;
            }
        }
        return true;
    }
    //递归法
    bool isValidBST(TreeNode* root) {
        if(root == NULL) return true;
        bool left=isValidBST(root->left);

        if(pre!=NULL && pre->val >= root->val) return false;
        pre=root;

        bool right=isValidBST(root->right);
        
        return left&&right;
    }

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

本人做法:
 对二叉搜索树进行中序遍历,结果输出到vector中,进行for循环,求相邻元素差的最小值即为最小绝对差。与98题一样,同样可以用递归和栈中序遍历解题,设置pre,求cur-pre的最小差值。

501.二叉搜索树中的众数*(Binary Search Tree)

递归代码如下:

class Solution {
public:
    int count=0;
    int maxcount=0;
    vector<int> res;
    TreeNode* pre=NULL;

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

        traversal(root->left);

        if(pre == NULL) count=1;
        else if(pre->val == root->val) count++;
        else{
            count=1;
        }
        pre=root;

        if(count == maxcount){
            res.push_back(root->val);
        }

        if(count > maxcount){
            maxcount=count;
            res.clear();
            res.push_back(root->val);
        }
        traversal(root->right);
        return;
    }

    vector<int> findMode(TreeNode* root) {
        count=0;
        maxcount=0;
        res.clear();
        pre=NULL;
        traversal(root);
        return res;
    }
};

 若是普通二叉树,可以通过三种遍历(递归即可),并由map统计val出现的次数,并设置一个vector,将pair<int,int>(值,出现的次数)添加到vector中,进行排序。求出出现频率最高的元素。
 若是二叉搜索树,则可以通过中序遍历进行求解。通过设置出现频率和最大出现频率,当当前频率大于最大频率时,更新最大频率,完成求解。

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

 本题用到了递归回溯思想,迭代法并不好模拟这种回溯思想,完成流程图如图所示:
在这里插入图片描述

class Solution {
public:
    //应后序遍历
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==NULL || root==p || root==q) return root;
        TreeNode* left=lowestCommonAncestor(root->left,p,q);
        TreeNode* right=lowestCommonAncestor(root->right,p,q);

        if(left!=NULL && right!=NULL) return root;
        else if(left==NULL && right!=NULL) return right;
        else if(left!=NULL && right==NULL) return left;
        else{
            return NULL;
        }
    }
};

那么我给大家归纳如下三点:

  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

  2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。

可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。

小结

现在已经讲过了几种二叉树了,二叉树,平衡二叉树,完全二叉树,二叉搜索树,后面还会有平衡二叉搜索树。 那么一些同学难免会有混乱了,我针对如下三个问题,帮大家在捋顺一遍:

  1. 平衡二叉搜索树是不是二叉搜索树和平衡二叉树的结合?
    是的,是二叉搜索树和平衡二叉树的结合。

  2. 平衡二叉树与完全二叉树的区别在于底层节点的位置?
    是的,完全二叉树底层必须是从左到右连续的,且次底层是满的。

  3. 堆是完全二叉树和排序的结合,而不是平衡二叉搜索树?
    堆是一棵完全二叉树,同时保证父子节点的顺序关系(有序)。但完全二叉树一定是平衡二叉树,堆的排序是父节点大于子节点,而搜索树是父节点大于左孩子,小于右孩子,所以堆不是平衡二叉搜索树

关于几种树的定义:
二叉树节点的深度和高度:
深度: 指从根节点到该节点的最长简单路径边的条数。
高度: 指从该节点到叶子节点的最长简单路径边的条数。
在这里插入图片描述

完全二叉树: 在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
在这里插入图片描述
平衡二叉树: 平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

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

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    它的左、右子树也分别为二叉搜索树。(有关二叉搜索树的题要想到中序遍历)

701.二叉搜索树中的插入操作(自解)

自解迭代代码如下:

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        TreeNode* newnode=new TreeNode(val);
        if(root == NULL){
            return newnode;
        }
        TreeNode* cur=root;
        bool flag=true;
        while(root != NULL){
            if(val<root->val){       
                if(root->left==NULL){
                    flag=true;
                    break;
                }
                root=root->left;
            }
            else if(val > root->val){
                if(root->right==NULL){
                    flag=false;
                    break;
                }
                root=root->right;
            }
        }
        
        if(flag==true){
            root->left=newnode;
        }
        else{
            root->right=newnode;
        }
        return cur;
    }
};

迭代代码:
 通过递归函数的返回值,完成了新加入节点的父子关系赋值操作。下一层将新加入的节点返回,本层用root->left或者root->right将其接住

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

题外话:
如果递归函数有返回值,如何区分搜索一条边还是搜索整个树?
搜索一条边的写法:

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

搜索整个树的写法:

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

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

需要考虑到五种情况:
没找到需要删除的节点:

  • 没找到需要删除的节点,遍历到空节点直接返回。

找到了需要删除的节点:

  • 左右孩子都为空(叶子节点),直接删除,return NULL。
  • 左孩子为空,右孩子不为空,直接删除节点,右孩子补位。
  • 右孩子为空,左孩子不为空,直接删除节点,左孩子补位。
  • 左右孩子都不为空,找到右孩子的左孩子的最左边子节点,将待删除节点的左孩子,充当其左孩子。

代码如下所示:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == NULL) return NULL;
        if(root->val == key){
            if(root->left==NULL && root->right==NULL){
                delete root;
                return nullptr;
            }
            else if(root->left==NULL && root->right!=NULL){
                TreeNode* node=root->right;
                delete root;
                return node;
            }
            else if(root->right == NULL){
                TreeNode* node=root->left;
                delete root;
                return node;
            }
            else{
                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;
            }
        }
	//这里相当于把新的节点返回给上一层,上一层就要用 root->left 
		//或者 root->right接住
        if(root->val < key){
            root->right=deleteNode(root->right,key);
        }
        if(root->val > key){
            root->left=deleteNode(root->left,key);
        }
        return root;
    }
};

下面代码相当于把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住。

        if(root->val < key){
            root->right=deleteNode(root->right,key);
        }
        if(root->val > key){
            root->left=deleteNode(root->left,key);
        }

669.修剪二叉搜索树

递归代码如下:

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

        root->left=trimBST(root->left,low,high);
        root->right=trimBST(root->right,low,high);
        return root;
    }
};

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

递归代码如下:

class Solution {
private:
    TreeNode* traversal(vector<int>& nums, int left, int right){
        //确定终止条件
        if(left > right) return nullptr;

        //确定单层递归逻辑
        int mid=left+(right-left)/2;
        TreeNode* node=new TreeNode(nums[mid]);
        node->left=traversal(nums,left,mid-1);
        node->right=traversal(nums,mid+1,right);
        return node;
    }
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode* root=traversal(nums, 0, nums.size()-1);
        return root;
    }
};

代码中用int mid=left+(right-left)/2代替了int mid=(left+right)/2,是为了防止越界问题的发生,因为right和left有可能是int最大值。当nums的size是偶数时,mid取左边。编程过程中,始终坚持左闭右闭原则。该题的思想与654.最大二叉树,450.删除二叉搜索树中的节点,701.二叉搜索树中的插入操作,比较相似。(牢记递归过程中,用本层的root->left或者root->right去接住上一层返回来的节点,同时避免在每层递归中创建vector浪费内存,应该利用数组下标进行编程

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

 刚开始看到这个题目的描述可能会有点蒙,看懂之后就会觉得豁然开朗。本题可通过逆中序遍历的方法,并设置一个全局的pre记录上一个节点的val,即可完成求解。在leetcode501.二叉搜索树中的众数、530.二叉搜索树的最小绝对差中,也采用了设置全局pre记录上一个节点的方法。

class Solution {
private:
    int pre;
    //逆中序遍历,右中左
    void traversl(TreeNode* root){
        if(root == NULL) return;

        traversl(root->right);
        root->val+=pre;
        pre=root->val;
        traversl(root->left);

        return;
    }

public:

    TreeNode* convertBST(TreeNode* root) {
        pre=0;
        traversl(root);
        return root;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值