LeetCode题解之二叉树(一)

164 阅读8分钟

1.二叉树基本知识

LeetCode中二叉树结构体:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

一颗二叉树的结构对称性和传递性太强了,天然的适用于递归调用的方法,例如dfs~

2.递归调用类题目-路径相关

104. 二叉树的最大深度(剑指 Offer 55 - I. 二叉树的深度)

class Solution {
public:
/*
1.dfs意义:返回以该节点为根结点的深度
2.对每个节点来说,所有可能的返回情况:
    节点为正常节点:返回左右子节点的最大深度+1:会包括以下两种情况
    节点为空节点:返回0
    节点为叶子节点:返回1
*/
    int maxDepth(TreeNode* root) {
        if(!root) return 0;//节点为空的情况返回0
        if(!root->left && !root->right) return 1;//叶子节点时返回1
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};

124. 二叉树中的最大路径和

/*
1.dfs意义:返回以该节点为根结点(最高节点)的最大单边路径(包含这个节点):如果<=0就返回0,也就是不取这个支路,否则返回上去后反倒更小
2.res存全局的变量:每次dfs一个点就算一下以当前点为最高节点的子树:left+right+root,取max
*/
class Solution {
public:
    int res = INT_MIN;
    int maxPathSum(TreeNode* root) {
        dfs(root);
        return res;
    }
    int dfs(TreeNode* root)
    {
        if(!root) return 0;
        int left = dfs(root->left);
        int right = dfs(root->right);
        res = max(res, left + right + root->val);//记录全局max
        return max(max(left, right) + root->val, 0);//
    }
};

543. 二叉树的直径

class Solution {
public:
    int ans = 0;
    int diameterOfBinaryTree(TreeNode* root) {
        //直径:最长的路径
        dfs(root);
        return ans;    
    }
    //dfs需要记录答案,并且返回往下的最大深度
    int dfs(TreeNode* root)
    {
        if(!root) return 0;
        auto left = dfs(root->left);
        auto right = dfs(root->right);
        ans = max(ans,left+right);//当前最高点的最大值是左+右,ans记录全局最高点的max
        return max(left+1,right+1);
    }
};

剑指 Offer 55 - II. 平衡二叉树

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

剑指 Offer 34. 二叉树中和为某一值的路径

/*
 带回溯的dfs
 */
class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    int s;
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        s = sum;
        dfs(root,0);//当前和,也可以直接传sum,然后做减法,判断是不是==0
        return res;
    }
    void dfs(TreeNode* root, int tmp)
    {
        if(!root) return ;//剪枝
        path.push_back(root->val);
        tmp += root->val;
        if(!root->left && !root->right && tmp == s) res.push_back(path);//剪枝
        dfs(root->left, tmp);//选择范围
        dfs(root->right, tmp);
        path.pop_back();//回溯
    }
};

3.递归调用类题目-子结构相关

剑指 Offer 26. 树的子结构

class Solution {
public:
/*
其实和矩形中找单词一样,先遍历根结点
*/
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        //遍历A树,挨个判断ai和bi为根结点的树是否相等
        if(!A || !B) return false;
        if(dfs(A,B)) return true;
        return isSubStructure(A->left,B) || isSubStructure(A->right,B);
    }
    bool dfs(TreeNode* p1, TreeNode* p2)
    {
        if(!p2) return true;//p2为空就true
        if(!p1) return false;//p2不为空但是p1为空,false
        if(p1->val == p2->val)
            return dfs(p1->left,p2->left) && dfs(p1->right, p2->right);
        return false ;
    }
};

剑指 Offer 28. 对称的二叉树

这道只有叶子都为空才返回true,上一道p2为空就表示可以了

/*
 dfs意义:以p1和p2为根结点的子树是否对称
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        //除了根结点以外,都是左右=右左
        return dfs(root->left,root->right);//这里只代表根结点的左右
    }
    bool dfs(TreeNode* p,TreeNode* q)//判断pq为根结点的是否对称
    {
        if(!p || !q) return !p && !q;//只有两个节点都为空时才返回true
        if(p->val != q->val) return false;
        return dfs(p->left, q->right) && dfs(p->right, q->left);
    }
};

剑指 Offer 27. 二叉树的镜像(226. 翻转二叉树)

/*
 dfs函数意义:把该节点为根结点的左右节点交换
 */
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(!root) return root;
        auto t = mirrorTree(root->left);
        auto p = mirrorTree(root->right);
        swap(root->left, root->right);//叶子节点其实直接执行这一步
        return root;
    }
};

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

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //到达叶节点下,返回null
        if(!root) return NULL;
        //如果找到了p或者q返回该节点
        if( root == p || root == q) return root;//无论p下是不是有q,都应该返回q
        //递归左右,返回找到了的节点或者null
        auto left = lowestCommonAncestor(root->left,p,q);
        auto right = lowestCommonAncestor(root->right,p,q);
        //pq分布在两边,返回根结点
        if(left && right) return root;
        //找到了其中一个节点:p或者q,返回这个节点
        if(left) return left;
        //返回另一个节点,顺便包含左右为没有的null的情况
        return right;
    }
};

129. 求根节点到叶节点数字之和

class Solution {
public:
    int sum = 0;
    int sumNumbers(TreeNode* root) {
        int path = 0;
        dfs(root, path);
        return sum;
    }
    void dfs(TreeNode* root, int path)
    {
        if(!root) return;
        path = path * 10 + root->val;
        if(!root->left && ! root->right)
        {
            sum += path;
            return;
        }
        dfs(root->left, path);
        dfs(root->right, path);
        //path -= root->val; 
    }
};

4.遍历二叉树

94. 二叉树的中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        //递归其实是系统开了栈来存储,时间o(n),非递归自己手动写栈
        stack<TreeNode*> stk;
        vector<int> res;
        auto p = root;
        while(p || stk.size())
        {
            while(p)
            {
                stk.push(p);//先压入所有左子树
                p = p->left;
            }
            p = stk.top();
            stk.pop();
            res.push_back(p->val);
            p = p->right;//对弹出的点,压入右子树的所有左子树
        }
        return res;
    }
};

144. 二叉树的前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        TreeNode* p = root;
        while(stk.size() || p)
        {
            while(p)
            {
                stk.push(p);
                res.push_back(p->val);
                p = p->left;
            }
            p = stk.top();
            stk.pop();
            p = p->right;
        } 
        return res;
    }
};

101. 对称二叉树

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        //迭代做法:左中右遍历左树,右中左遍历右树
        if(!root) return true;
        stack<TreeNode*> left,right;
        auto l = root->left,r = root->right;
        while(l || r || left.size() || right.size())
        {
            while(l && r) 
            {
                left.push(l), right.push(r);
                l = l->left, r = r->right;
            }
            if(l || r) return false;
            l = left.top(), left.pop();
            r = right.top(), right.pop();
            if(l->val != r->val) return false;
            l = l->right, r = r->left;//对每个弹出的节点,把它的左子树全部压入
        } 
        return true;
    }
    
};

剑指 Offer 32 - I. 从上到下打印二叉树

/*
 队列先进先出:root入队,对每一层的节点,每遍历一个就把下一层的加入队列
 */
class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        vector<int> res;
        if(!root) return res;
        queue<TreeNode*> q;//注意:是*
        q.push(root);
        
        while(!q.empty())
        {
            auto root = q.front();
            if(root->left) q.push(root->left);
            if(root->right) q.push(root->right);
            res.push_back(q.front()->val);
            q.pop();
        }
        return res;
    }
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

/*
 遍历一层的时候,把下一层的节点单独存在另一个队列里;一层遍历完再交替
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        vector<int> tmp;
        if(!root) return res;
        queue<TreeNode*> q;//注意:是*
        queue<TreeNode*> nex, empty;
        q.push(root);
        
        while(!q.empty())
        {
            auto root = q.front();
            if(root->left) nex.push(root->left);
            if(root->right) nex.push(root->right);
            tmp.push_back(q.front()->val);
            q.pop();
            if(q.empty())
            {
                q = nex;
                nex = empty;
                res.push_back(tmp);
                tmp = {};
            }
        }
        return res;
    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

/*
 遍历一层的时候,把下一层的节点单独存在另一个队列里;一层遍历完再交替,交替的时候注意是否反转
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        vector<int> tmp;
        if(!root) return res;
        queue<TreeNode*> q;//注意:是*
        queue<TreeNode*> nex, empty;
        q.push(root);
        int t = 0;
        while(!q.empty())
        {
            auto root = q.front();
            if(root->left) nex.push(root->left);
            if(root->right) nex.push(root->right);
            tmp.push_back(q.front()->val);
            q.pop();
            if(q.empty())
            {
                q = nex;
                nex = empty;
                if(t % 2) reverse(tmp.begin(), tmp.end());//多了判断的地方
                t ++;
                res.push_back(tmp);
                tmp = {};
            }
        }
        return res;
    }
};

114. 二叉树展开为链表

class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root) return;
        auto p = root;
        while(p)
        {
            if(p->left) 
            {
                auto tmp = p->right;
                auto l = p->left;
                p->left = nullptr;//1.注意要把left赋成空
                p->right = l;//2.插入左端点      
                while(l->right) l = l->right;//3.找到左子树最右边的端点
                l->right = tmp;//4.该端点的右端点指向以前的右端点       
            }
            p = p->right;//继续遍历右端点
        }
    }
};

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

class Solution {
public:
    int sum = 0;
    TreeNode* convertBST(TreeNode* root) {
    if(root != nullptr)   
    {
        convertBST(root->right);
        sum += root->val;
        root->val = sum;//要修改每一个值
        convertBST(root->left);
    }
    return root;

    }
};

5.还原二叉树

105. 从前序与中序遍历序列构造二叉树(剑指 Offer 07. 重建二叉树)

class Solution {
public:
    unordered_map<int, int> pos;//map直接在中序遍历中找到对应的下标
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = inorder.size();
        for(int i = 0; i < n; i++) pos[inorder[i]] = i;
        return dfs(preorder, inorder, 0, n-1, 0, n-1);
    }
    TreeNode* dfs(vector<int>& preorder, vector<int>& inorder, int pl, int pr, int li, int lr)
    {
        if(pl > pr) return NULL;
        int val = preorder[pl];
        auto root = new TreeNode(val);
        int index = pos[val];
        auto left = dfs(preorder, inorder, pl + 1,index - li + pl, li, index - 1);
        auto right = dfs(preorder,inorder,index-li+pl+1,pr,index+1,lr);
        root->left = left, root->right = right;
        return root;
    }
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

class Solution {
public:
    vector<int> seq;
    bool verifyPostorder(vector<int>& postorder) {
        //二叉搜索树:左边都小,右边都大
        seq = postorder;
        return dfs(0, seq.size() - 1);
    }
    bool dfs(int l, int r)
    {
        if(l >= r) return true;
        int root = seq[r];//最后一个点是根结点,
        int k = l;
        while(k < r && seq[k] < root) k++;//找到右子树第一个点
        for(int i = k; i < r; i ++) //右子树如果有比根结点小的就不对
            if(seq[i] < root)
                return false;
        //否则当前这层合法
        //cout<<l<<" "<<k<<" "<<r<<" "<<endl; 
        return dfs(l, k - 1) && dfs(k, r - 1);//判断左子树和右子树
    }
};

6.其余二叉搜索树类

98. 验证二叉搜索树

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        return dfs(root, INT_MIN, INT_MAX);
    }
    bool dfs(TreeNode* root, long long minv, long long maxv)
    {
        if(!root) return true;//root为空
        if(root->val < minv || root->val > maxv) return false;//root越界,当前区间是[minv,maxv]
        //注意递归的区间,传入的是可以相等的,所以左子树要<当前root的val,val可能是负无穷,减1会溢出,所以用long long的1
        return dfs(root->left, minv, root->val-1ll) && dfs(root->right, root->val+1ll, maxv);//递归判断左右子树范围
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

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

剑指 Offer 54. 二叉搜索树的第k大节点

class Solution {
public:
    TreeNode* ans;
    int kthLargest(TreeNode* root, int k) {
        //按照中序遍历,传入k即可
        dfs(root, k);
        return ans->val;
    }
    void dfs(TreeNode* root,int &k)//k一定要是全局的,才能一直减所以是&!!!
    {
        if(!root) return;
        dfs(root->right, k);
        k --;//直到遍历到右下角的叶节点才第一次-- 
        if(!k) ans = root;//减完就判断
        dfs(root->left, k);
    }
};

96. 不同的二叉搜索树

class Solution {
public:
    int numTrees(int n) {
        //o(n^2),因为n个值需要求解,每次求解需要o(n)复杂度
        vector<int> f(n + 1, 0);
        f[0] = 1;
        f[1] = 1;
        for(int i = 2; i <= n; i ++)//就是一纬的,每次是前面的组合
            for(int j = 1; j <= i; j ++)
            {
                f[i] += f[j - 1] * f[i - j];
            }
        return f[n];
    }
};