二刷力扣——二叉树

94. 二叉树的中序遍历

迭代遍历。

法1

学会一种颜色遍历法:前中后3种就改一下入栈顺序(遍历顺序倒过来)就OK,非常统一。

不过空间比普通迭代多一点,因为stack存储的是pair。

力扣高赞题解:

a2a8f9e338bb407eb0d0ee436e43de03.png

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        //递归算法很简单,你可以通过迭代算法完成吗?
        stack< pair<TreeNode*,bool> > st;
        vector<int> ans;
        st.push(make_pair(root,0));
        while(!st.empty())
        {
            pair<TreeNode*,bool> pp=st.top();
            st.pop();
            if(pp.first==nullptr)continue;    //叶节点的孩子节点

            if(pp.second==0)
            {
                st.push(make_pair(pp.first->right,0));
                st.push(make_pair(pp.first,1));
                st.push(make_pair(pp.first->left,0));
            }
            else //左右孩子已经放入
            {
                ans.push_back(pp.first->val);
            }
        }
        return ans;
    }
};

法2  普通迭代

前序和后序就是层次遍历的队列换成栈,孩子入栈顺序是先右再左,对应栈。

class Solution {
public:
    
    vector<int> preorderTraversal(TreeNode* root) {
        //递归算法很简单,你可以通过迭代算法完成吗?
        vector<int> ans;
        if(!root)return ans;

        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* pp=st.top();
            st.pop();
            ans.push_back(pp->val);
            if(pp->right)st.push(pp->right);
            if(pp->left)st.push(pp->left);
        }
        return ans;

    }
};

不过后序的需要转换一下,左右中<-中右左反转。所以前序孩子入栈顺序改一下,最后再反转ans即可。

720f94df5c88495ca18f7adacc46cfdc.png

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        //递归算法很简单,你可以通过迭代算法完成吗?
        vector<int> ans;
        if(!root)return ans;

        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* pp=st.top();
            st.pop();
            ans.push_back(pp->val);
            if(pp->left)st.push(pp->left);
            if(pp->right)st.push(pp->right);
            
        }
        reverse(ans.begin(),ans.end());
        return ans;
    }
};

中序迭代就和前后不一样,所以还是看法1吧。

其实这种方法的本质是每个节点都要入栈两次后才能访问其元素值,第一次入栈是不能访问其值的,因为第一次入栈是第一次访问该节点,需要先访问该节点的左子树,这时会把该结点和左子树都入栈,所以第二次出栈就可以访问该结点的值啦。

栈的结构使得每次节点肯定是按中左右要求出来的。

104. 二叉树的最大深度

class Solution {
public:
    int maxDepth(TreeNode* root) {
        //用层次遍历
        if(!root)return 0;
        TreeNode* last=root;
        queue<TreeNode *> q;
        q.push(root);
        int count=1;
        while(true)
        {
           TreeNode* p = q.front();
           q.pop();
           if(p->left)q.push(p->left);
           if(p->right)q.push(p->right);
           if(q.empty())break;
           if( p==last) {
                last=q.back();
                count++;
           }
        }
        return count;
    }
};

如果p==last没加对q判空的条件,count应该初始化为0.

111. 二叉树的最小深度

用一个zz标记每层最后一个,minh代表当前节点cur的当前高度,如果当前节点没有左右孩子,直接返回minh。

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        queue<TreeNode*> q;
        q.push(root);
        int minh=1;
        TreeNode* zz=root;
        while(!q.empty())
        {
            TreeNode* cur= q.front();
            q.pop();
            if(cur->left)q.push(cur->left);
            if(cur->right)q.push(cur->right);
            if(!cur->left && !cur->right)return minh;//minh:cur这层的高度
            if(zz==cur){
                zz=q.back();//追踪每层最后一个
                minh++;
            }
            
        }
        return minh;
    }
};

226. 翻转二叉树

学会用上面遍历的递归和迭代模板。

单参数递归。

  1. 法1。

递归:反转完孩子,再交换左右孩子。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(!root)return nullptr;
        //递归
        TreeNode  * l=invertTree(root->left);//反转左孩子为首的
        TreeNode  * r=invertTree(root->right);//反转右孩子为首的
        root->left=r;   //交换左右孩子
        root->right=l;
        return root;
    }
};

先处理自己的反转,再处理孩子的反转也可以:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(!root)return nullptr;
        swap(root->left,root->right);//交换左右孩子
        //递归
        invertTree(root->left);//反转左孩子为首的
        invertTree(root->right);//反转右孩子为首的
        return root;
        
    }
};

时间/空间 复杂度:O(N)

  1. 法2。

迭代。

颜色法

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
         //递归算法很简单,你可以通过迭代算法完成吗?
        stack< pair<TreeNode*,bool> > st;
        //vector<int> ans;
        st.push(make_pair(root,0));
        while(!st.empty())
        {
            pair<TreeNode*,bool> pp=st.top();
            st.pop();
            if(pp.first==nullptr)continue;    //叶节点的孩子节点

            if(pp.second==0)
            {
                st.push(make_pair(pp.first->right,0));
                st.push(make_pair(pp.first->left,0));
                st.push(make_pair(pp.first,1));
            }
            else //左右孩子已经放入
            {
                swap(pp.first->left,pp.first->right);
            }
        }
        return root;
        
    }
};

101. 对称二叉树

检查轴对称,意思是值val相等,不是节点直接相等。

还是可以递归写。

双参数递归。

递归函数表示:2个节点开头的子树是不是轴对称,重写递归函数,传入参数是left和right。

先把可排除的都排除,再递归左子树和左子树和右子树的右子树是否相等……

class Solution {
public:
    bool isDuic(TreeNode* l, TreeNode* r)//l和r开头的2个树对不对称
    {
        if(!l && !r)return true;        //都为空
        else if(!l ||!r)return false;
        if(l->val!=r->val)return false;           //不相等
        bool ll=isDuic(l->left,r->right);
        bool rr=isDuic(l->right,r->left);
        return  ll & rr;
    }

    bool isSymmetric(TreeNode* root) {
        if(!root)return true;
        return isDuic(root->left,root->right);
    }
};

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

一刷的时候用的递归。

二刷看题解,是二分查找+位运算,还是比较复杂(主要是位运算)。

先走到最左下节点,是最后一层的第1个节点,设想第1个节点编码是100,满二叉树的最后一个节点是111。

0fde2c84e3f44836a3721a9b46cdab92.jpeg

二分运算在mid处判断的条件是,mid在不在这棵树里面,如果在往右边再判断。否则,往左走。最后mid为空或者left>right了停止。

这里面low、high、mid都是二进制表示,代表了root到自己的路径。

class Solution {
public:
    int countNodes(TreeNode* root) {
        if (root == nullptr) {
            return 0;
        }
        int level = 0;
        TreeNode* node = root;
        while (node->left != nullptr) {
            level++;
            node = node->left;
        }
        int low = 1<<level,high=(1<<(level+1))-1;    //初始赋值
        while(low<high)
        {
            int mid=(high - low + 1)/2+ low;    //相加÷2错
            if(exists(root,level,mid))    //high 和 mid 都想指向存在的节点
            {
                low=mid;
            }
            else high=mid - 1;
        }
        return low;            //指向最后一个存在的节点
    }    

    bool exists(TreeNode* root, int level, int k) {
        int bits=1<<(level-1);
        TreeNode* p=root;
        while(p !=nullptr && bits>0)    //到空节点了或者到路径最后了,退出
        {
            if(!(bits & k))p=p->left;    //0指向左
            else p=p->right;
            bits >>= 1;
        }
        return (p!=nullptr);        //到空节点退出的,说明节点不存在

    }
};

注意:

1、判断某一位是0/1的措施(bits只有1个“1”,相与判断)。

2、low和high都想要指向存在的节点。

110. 平衡二叉树

递归:

自底向上递归的做法类似于后序遍历,对于当前遍历到的节点,先递归地判断其左右子树是否平衡,再判断以当前节点为根的子树是否平衡。如果一棵子树是平衡的,则返回其高度(高度一定是非负整数),否则返回 −1-1−1。如果存在一棵子树不平衡,则整个二叉树一定不平衡。

不平衡的话,返回-1;

if(abs(l-r)>=2)return -1;

自己的子树不平衡的话,也返回-1:

if(l==-1)//左边非平衡
                {
            return -1;
        }
if(r==-1) return -1;//右边非平衡

否则返回子树(包括自己)的高度。

return max(l,r)+1;
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int isBal(TreeNode* root){
        if(!root)return 0;
        int l=isBal(root->left);
        //有一边不平衡了就可以直接返回-1了
        if(l==-1)//左边非平衡
        {
            return -1;
        }
        int r=isBal(root->right);
        if(abs(l-r)>=2)return -1;
        
        if(r==-1) return -1;//右边非平衡
        
        return max(l,r)+1;
    }
    bool isBalanced(TreeNode* root) {
        
        return isBal(root)>=0;
    }
};

257. 二叉树的所有路径

回溯法。

或者看做前序遍历

先处理自己这个节点——1、为空。2、左右孩子都为空,可以收集了。只是收集,不pop_back(),因为后面对左右的操作会pop_back(),不能重复。这就导致vector<int>里面始终存放着root节点。别的节点不会删2次。所以path一开始就得把root放进去。

再回溯左孩子、右孩子。这里回溯写了2次,之后的回溯就是循环n次。

回溯循环:push-back(),递归,pop_back()。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<string> result;
    void backtrack(vector<int> &luj,TreeNode* root)
    {
        if(!root)return ;
        if(!root->left && !root->right)
        {
            string s="";
            for(int i=0;i<luj.size();++i)
            {
                s+=to_string(luj[i]);
                if(i<luj.size()-1)s+="->";
            }
            result.push_back(s);
            return;
        }
        if(root->left)
        {
            luj.push_back(root->left->val);
            backtrack(luj,root->left);
            luj.pop_back();
        }

        if(root->right)
        {
            luj.push_back(root->right->val);
            backtrack(luj,root->right);
            luj.pop_back();
        }
        
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<int> luj;
        luj.push_back(root->val);
        backtrack(luj,root);
        return result;
    }
};

404. 左叶子之和

这道题目竟然卡了四十分钟,不像之前只是处理当前节点,这里的递归出口,除了空节点,还有就是左子树就是叶子的情况,那么此时l直接==root->left->val。否则得依靠递归来收集l这个值。

右子树的话,不能立即判断,即这里不能写递归出口,所以只能靠递归来得到r。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {//左子树的左叶子+右子树的左叶子
        if(!root)return 0;
        int l=0,r=0;
        if(root->left){
            if(!root->left->left&&!root->left->right)l=root->left->val;//左子树是一个叶子结点,直接加
            else l=sumOfLeftLeaves(root->left);
        }
        if(root->right)r=sumOfLeftLeaves(root->right);
        return l+r;
    }
};

513. 找树左下角的值

法1:用last指针记录一层。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        //迭代
        queue<TreeNode*> q;
        q.push(root);
        TreeNode* last=root;
        TreeNode* first=root;
        while(!q.empty())
        {
            TreeNode* cur = q.front();
            q.pop();
            if(cur->left)q.push(cur->left);
            if(cur->right)q.push(cur->right);
            if(last==cur && !q.empty()){
                first=q.front();
                last=q.back();
            }
        }
        return first->val;
    }
};

法2:不用last只用first记录每一层第一个节点。是用循环,一层循环就一个树的层次。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> q;
        q.push(root);
        TreeNode* first = root;
        while (!q.empty()) {
            int size = q.size();
            first = q.front(); // 记录当前层的第一个节点
            for (int i = 0; i < size; ++i) {
                TreeNode* cur = q.front();
                q.pop();
                if (cur->left) q.push(cur->left);
                if (cur->right) q.push(cur->right);
            }
        }
        return first->val;
    }
};

或者改变传统层次遍历,从右到左遍历,最后的节点就是所求节点,也不用2个指针。

112. 路径总和

依然是回溯,但是对于统计路径上的和sum,有2种方法:

1、跟path一致,往下到一个节点就加入,往上走又减出:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    
    bool exist=false;
    vector<int> path;
    void backtrack(TreeNode* root , int targetSum,int sum)
    {
        if(!root)return ;
        if(!root->left && !root->right){
            if(sum==targetSum) {
                exist=true;
            }
            return;
        }
        if(root->left){
            sum+=root->left->val;     
            path.push_back(root->left->val);
            backtrack(root->left,targetSum,sum);
            path.pop_back();
            sum-=root->left->val;
        }
        if(root->right){
            sum+=root->right->val;
            path.push_back(root->right->val);
            backtrack(root->right,targetSum,sum);
            path.pop_back();
            sum-=root->right->val;
        }
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root)return false;
        int sum=root->val;
        path.push_back(root->val);
        backtrack(root,targetSum,sum);
        return exist;
    }
};

2、跟 257. 二叉树的所有路径 一样,到达叶子节点了,再统一把这一条路径(整个path)里面的nia下来,sum统计求和。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int sum=0;
    int totalsum=-1001;
    vector<int> path;
    void backtrack(TreeNode* root , int targetSum)
    {
        if(!root)return ;
        if(!root->left && !root->right){
            sum=0;
            for(int i=0;i<path.size();++i){
                sum+=path[i];
            }
            if(sum==targetSum) {
                totalsum=sum;
            }
            return;
        }
        if(root->left){

            path.push_back(root->left->val);
            backtrack(root->left,targetSum);
            path.pop_back();
        }
        if(root->right){

            path.push_back(root->right->val);
            backtrack(root->right,targetSum);
            path.pop_back();
        }
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root)return false;
        path.push_back(root->val);
        backtrack(root,targetSum);
        return totalsum==targetSum;
    }
};

更简便的写法,递归,隐式回溯:

class Solution {
public:
    //不用辅助函数实现,简便递归
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root)return false;
        if(!root->left && !root->right)return root->val==targetSum;//叶子节点,判断
        return hasPathSum(root->left,targetSum-root->val)||hasPathSum(root->right,targetSum-root->val);//左/右子树有没有targetSum-root->val的路径,
    }
};

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

递归数组下标的确定。

基本原理是能确定后序数组里面的最后一个是根节点,然后把中序数组中,根节点左右分开。必须先构造右子树,再构造左子树。因为下次递归还是取后续数组的最后元素。

应该先构造右子树再构造左子树,因为得从右到左取postorder的最后元素构造节点;而且也不用传递子数组,子树的序列用a和b可以表示。

题目说了没有重复元素存在。

注意back()值的下标是用find返回元素迭代器之后,减去begin()得到相对距离——即下标;

auto it=find(inorder.begin(),inorder.end(),data);
int index=it-inorder.begin();
class Solution {
public:
    TreeNode* build(vector<int>& inorder, vector<int>& postorder,int a,int b){
        //递归
        if(a>=b)return nullptr;
        int data=postorder.back();
        postorder.pop_back();
        
        TreeNode* root=new TreeNode(data);//根节点
        auto it=find(inorder.begin(),inorder.end(),data);
        int index=it-inorder.begin();

        root->right=build(inorder,postorder,index+1,b);//先右
        root->left=build(inorder,postorder,a,index);//后左
        
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        TreeNode* root=build(inorder,postorder,0,inorder.size());//左闭右开
        return root;
    }
};

a和b就是判断一下空节点

105. 从前序与中序遍历序列构造二叉树

前序和中序也是一样,由于每次就构造前序的front()那一个节点,所以翻转一下preorder,方便用pop_back()。

class Solution {
public:
    TreeNode* build(vector<int>& preorder, vector<int>& inorder,int a,int b,int c)
    {
        if(a>=b)return nullptr;
        int data=preorder.back();//本次递归就构造preorder[c]这个节点
        preorder.pop_back();
        TreeNode * root=new TreeNode(data);
        auto it=find(inorder.begin(),inorder.end(),data);
        int index=it-inorder.begin();
        c++;
        root->left=build(preorder,inorder,a,index,c);
        root->right=build(preorder,inorder,index+1,b,c);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        reverse(preorder.begin(),preorder.end());
        TreeNode* root=build(preorder,inorder,0,preorder.size(),0);
        return root;
    }
};

654. 最大二叉树

一样的做法,参数a、b是子树的节点在nums的左右边界。

class Solution {
public:
    TreeNode* construct(vector<int>& nums,int a,int b)
    {
        if(a>=b)return nullptr;
        int maxData=INT_MIN,index=0;
        for(int i=a;i<b;++i)
        {
            if(nums[i]>maxData){
                maxData=nums[i];
                index=i;
            }
        }
        TreeNode* root=new TreeNode(maxData);
        root->left=construct(nums,a,index);
        root->right=construct(nums,index+1,b);
        return root;
    }
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return construct(nums,0,nums.size());//左闭右开
    }
};

构造二叉树的模板。

617. 合并二叉树

当前2给节点全为空才返回。时间复杂度:O(max(m,n)).


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

优化,条件可以不写这么多,分别判断root1和root2是否为空即可。这样时间复杂度是O(min(m,n))。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(!root1 )return root2;
        if(!root2 )return root1;
        TreeNode* root=new TreeNode(root1->val+root2->val);//合并root1节点和root2节点
        root->left=mergeTrees(root1->left,root2->left);
        root->right=mergeTrees(root1->right,root2->right);
        return root;
    }
};

再优化,直接在root1/root2上面修改,不创建新的节点。

class Solution {
public:
    
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(!root1 )return root2;
        if(!root2 )return root1;
        root1->val+=root2->val;//合并root1节点和root2节点
        root1->left=mergeTrees(root1->left,root2->left);
        root1->right=mergeTrees(root1->right,root2->right);
        return root1;
    }
};



98. 验证二叉搜索树

法1:

一开始就递归判断左右孩子和自己的大小关系,但是错误。

二叉搜索树:

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

是左子树和右子树的所有节点 和 自己的大小关系符合条件才对。

二叉搜索树的充分必要条件:中序遍历的序列是严格递增。所以中序递归遍历的过程中和之前序列里面的最大值比较,如果小了,就返回false。左右子树的递归函数位置满足中序遍历,而且左右子树只要有为false的,就返回false。

题目的节点里面有小于INT_MIN的数,所以maxVal必须初始化为更小的值:

class Solution {
public:
    long maxVal=LONG_MIN;
    bool isValidBST(TreeNode* root) {
        //中序遍历是从小到大
        if(!root)return true;
        
        if(!isValidBST(root->left))return false;
        if(maxVal>=root->val)return false;
        maxVal=root->val;
        if(!isValidBST(root->right))return false;

        return true;
    }
};

遍历的话,再复习一下颜色标记法 迭代方法:
 

class Solution {
public:
   
    bool isValidBST(TreeNode* root) {
        stack< pair<TreeNode*,bool> > st;
        vector<int> ans;
        st.push(make_pair(root,0));
        long maxVal=LONG_MIN;
        while(!st.empty())
        {
            pair<TreeNode*,bool> pp=st.top();
            st.pop();
            if(pp.first==nullptr)continue;    //叶节点的孩子节点

            if(pp.second==0)
            {
                st.push(make_pair(pp.first->right,0));
                st.push(make_pair(pp.first,1));
                st.push(make_pair(pp.first->left,0));
            }
            else //左右孩子已经放入
            {
                if(maxVal >=pp.first->val)return false;
                maxVal=pp.first->val;
            }
        }
        return true;
    }
};

法2:

工具函数:helper(TreeNode* root,long long a,long long b);左边最大值和右边最小值进行比较,递归是修改a和b的参数,达到左子树和右子树的局部判断。更简洁:

class Solution {
public:
    //a:记录左边最大的;b:记录右边最小的
    bool helper(TreeNode* root,long long a,long long b)
    {
        if(!root)return true;
        if(root->val<=a || root->val >=b)return false;
        return helper(root->left,a,root->val)&&helper(root->right,root->val,b);
    }
    bool isValidBST(TreeNode* root) {
        return helper(root,LONG_MIN,LONG_MAX);
    }
};

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

还是利用二叉搜索树中序遍历有序的q

class Solution {
public:
    int minVal=INT_MAX,last=100001;//节点值最大是10^5
        
    int getMinimumDifference(TreeNode* root) {
        //中序遍历更新minVal和last
        if(!root)return 100001;
        int l=getMinimumDifference(root->left);
        minVal=min(minVal,abs(root->val-last));
        last=root->val;
        int r=getMinimumDifference(root->right);
        return minVal;
    }
};

时间复杂度:O(n)。

空间复杂度:最多是O(n),此时二叉树退化成链状。

501. 二叉搜索树中的众数

自己的想法也是中序遍历,然后比较maxcount(一开始想的lastCount,wrong)和count,更新的逻辑写的有问题导致总是有元素不能包含在result里面。

查看题解,对当前节点的操作从中序遍历中分离出来:

b0d1ecd99cf649b6a41d6222f0ca36a7.png

分开更新base和count,maxCount,更清晰。

更新这三个值的部分就是分离出来的,对当前节点的处理,没有存储last,只会把base放进数组里面。

class Solution {
public:
    int last, count = 0, maxCount = -1;
    vector<int> result;

    void update(TreeNode* root) {
        if(root->val ==last )
        {
            count++;
        }
        else {
            last=root->val;
            count = 1;
        }
        if(count > maxCount){
            result = vector<int>{root->val};
            maxCount=count;
        }
        else if(count == maxCount)result.push_back(root->val);
    }

    void zhongxu(TreeNode* root){
        if(!root)return ;
        zhongxu(root->left);
        update(root);
        zhongxu(root->right);
    }

    vector<int> findMode(TreeNode* root) {
        zhongxu(root);
        return result;
    }
};

但是这样,有count开始超过maxCount开始,相同的数会反复clear()了又push_back()进数组。代码简洁但是时间有点多,不过时间复杂度肯定是O(n)。

如果last存储上个节点,代码变成:(其实差不多的逻辑)

class Solution {
public:
    TreeNode* pre=nullptr;
    vector<int> v;
    int maxCount=0,count=0;
    void traversal(TreeNode* cur){
        if(!cur)   return ;
        traversal(cur->left);
        if(!pre||pre->val!=cur->val)count=1;
        else count++;
        if(count>maxCount){
            maxCount=count;
            v.clear();
            v.push_back(cur->val);
        }
        
        else if(count==maxCount)v.push_back(cur->val);
        pre=cur;
        traversal(cur->right);
    }
    vector<int> findMode(TreeNode* root) {
        traversal(root);
        return v;
    }
};

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

明天再理解一下其他解法,力扣官方的其实也还行。主要原理就是,最近公共祖先一定是左子树一个右子树一个(这里左右子树包括子树的根),找到了就赋值为ans;如何判断左子树一个右子树一个,用函数houxu()后序遍历一下,返回值是bool型,就欧克。

ans只有一次赋值,赋值了ans之后,不会改变了。因为最近公共祖先,一定是左一个右一个这个p和q,其他祖先,要么pq都在祖先左边,要么都在祖先右边。

class Solution {
public:
    TreeNode* ans;
    bool houxu(TreeNode* root, TreeNode* p, TreeNode* q)//root子树下面有没有p和q
    {
        if(!root)return false;
        bool l= houxu(root->left,p,q);
        bool r= houxu(root->right,p,q);
        if((l && r)||((p==root||q==root)&&(l || r))){//找到
            ans = root;//只会赋值一次
        }
        return l||r||(p==root)||(q==root);//左/右子树有p、q或者自己是p、q
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        houxu(root,p,q);
        return ans;
    }
};

时间复杂度和空间复杂度:O(n)

之前的一刷是一个回溯一个递归,感觉不容易理解。

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

不同的是:二叉搜索树。

关键是怎么利用搜索树这个性质?

搜索树里面,一个数的查找,是从上到下。2个的话,也可以从上到下。和上面一样,一旦找到ans,就直接返回了,因为我们从上倒下找的,第一个遇到的[p->val,q->val]之间的值,一定是最近公共祖先。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ans=nullptr,*cur=root;
        int pqmax=max(q->val,p->val);
        int pqmin=min(q->val,p->val);
        while(cur)
        {
            if(cur->val <pqmin)cur=cur->right;
            else if(cur->val > pqmax)cur=cur->left;
            else return cur;
        }
        return nullptr;
    }
};

也可以递归实现,逻辑一样,要么去左边,要么去右边,否则返回该节点。

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

这两道,一个从下到上找到的第一个符合条件的,就是ans,因为一般二叉树只有一个节点满足这个判断条件。一个从上到下找到的第一个符合条件的,是ans,因为瑶用到搜索树的性质,有多个在这个区间内的(p和q的值之间),但是从上到下第一个遇到的满足区间的节点,必定就是最近公共祖先。注意性质的不同。

450 删除二叉树节点(搜索树)

删除比插入麻烦一些

递归,GPT4帮忙修改:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return nullptr;

        // Find the node to be deleted
        if (key < root->val) {
            root->left = deleteNode(root->left, key);
        } else if (key > root->val) {
            root->right = deleteNode(root->right, key);
        } else {
            // Node to be deleted found
            if (!root->left) {
                // Node has no left child
                TreeNode* rightChild = root->right;
                delete root;
                return rightChild;
            } else if (!root->right) {
                // Node has no right child
                TreeNode* leftChild = root->left;
                delete root;
                return leftChild;
            } else {
                // Node has two children
                // Find the in-order successor (smallest node in the right subtree)
                TreeNode* successor = findMin(root->right);
                root->val = successor->val;
                root->right = deleteNode(root->right, successor->val);
            }
        }

        return root;
    }

private:
    TreeNode* findMin(TreeNode* node) {
        while (node->left) node = node->left;
        return node;
    }
};

一刷也是递归写的,我用迭代,报错Error - Found cycle in the TreeNode...

可以用迭代写,但是很冗长,还是用递归吧。

思路还是挺清晰的,有没有左右孩子 有三种情况。3种情况里面,主要是==key的时候,删除的过程是:找到前驱或者是后继节点,root的值替换成这个,然后再递归删除左子树或者二是右子树里面的这个前驱或者是后继节点 (cur)就ok。迭代麻烦在这里左子树右子树也是递归删除不是一个delete就能解决的。

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(!root)return nullptr;
        if(root->val == key)
        {
            //分为没有孩子、有左子树、没左子树有右子树3种情况
            if(!root->left && !root->right){
                delete(root);
                return nullptr;
            }
            else if(root->left)
            {
                TreeNode* cur=root->left;//左子树的最右下
                while(cur->right)cur=cur->right;
                root->val=cur->val;
                //delete(cur);wrong,cur不一定没有孩子,得用递归删除
                root->left=deleteNode(root->left,cur->val);
            }
            else if(root->right)
            {
                TreeNode* cur=root->right;//右子树的最左下
                while(cur->left)cur=cur->left;
                root->val=cur->val;
                root->right=deleteNode(root->right,cur->val);
            }
        }
        else if(root->val > key){
            root->left=deleteNode(root->left,key);
        }
        else {
            root->right=deleteNode(root->right,key);
        }
        return root;
    }
};

669. 修剪二叉搜索树

root如果不在范围里面,比如在low左边,肯定root->left也在low左边,自己和root->left全部砍掉,不用挨个delete,砍掉之后是右子树继承自己,那么直接返回处理后的右子树就行(递归),在high右边也是一个道理。

如果在范围里面,左右子树还是需要处理的,因此修改左右孩子=递归后的节点。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root)return nullptr;
        if(root->val<low){//范围外
            return trimBST(root->right,low,high);
        }
        else 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;
    }
};

是深度优先遍历,下面这样更清晰,并不是一定能跟前序中序后序做一个对应,不是所有节点都会处理到,只是也多种情况,所以还是情况1、2、3列出来,在每种情况下知道要干嘛,就知道要不要用递归。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root)return nullptr;
        if(root->val>=low && root->val <=high )
        {
            root->left=trimBST(root->left,low,high);
            root->right=trimBST(root->right,low,high);
            return root;
        }
        else if(root->val<low){//范围外
            return trimBST(root->right,low,high);
        }
        return trimBST(root->left,low,high);
       
    }
};

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

8edfd763011a423dafc38569e7dc769f.png

有3种选择中心节点的方法:pos=(left+right)/2;pos=(left+right+1)/2;随机选前2种中的一种。


class Solution {
public:
    TreeNode* help(vector<int>& nums,int a,int b) {
        if(a>b)return nullptr;
        int pos=(a+b)/2;
        TreeNode* cur=new TreeNode(nums[pos]);
        cur->left=help(nums,a,pos-1);
        cur->right=help(nums,pos+1,b);
        return cur;
    }
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        //平衡
        return help(nums,0,nums.size()-1);
    }
};

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

这题倒是简单,用递归很明显的,使用类成员变量sum统计即可,注意是 右中左 的顺序。

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值