【2023/2/28~3/4 Leetcode】二叉树练习集锦

学习链接:

  • 二叉树(纲领版)
  • 未解决【困难】:
    987. 二叉树的垂序遍历
    968. 监控二叉树

1.前序遍历构造二叉搜索树

题目来源:1008.前序遍历构造二叉搜索树
题解:

/**
 * 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* bstFromPreorder(vector<int>& preorder) {
        return build(preorder,0,preorder.size()-1);
    }
    TreeNode* build(vector<int>& preorder,int start,int end){
        if(start>end)
            return nullptr;
        int val=preorder[start];
        TreeNode *root=new TreeNode(val);
        int p=start+1;
        while(p<=end&&preorder[p]<val)
            p++;
        root->left=build(preorder,start+1,p-1);
        root->right=build(preorder,p,end);
        return root;
    }
};

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

题目来源:105.从前序与中序遍历序列构造二叉树
题解:

class Solution {
private:
    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

2.根到叶路径上的不足节点

题目来源:1080. 根到叶路径上的不足节点
题解:

class Solution {
public:
    TreeNode* sufficientSubset(TreeNode* root, int limit) {
        if(root==nullptr)
            return nullptr;
        if(root->left==root->right)
        {
            if(root->val<limit)
                return nullptr;
            return root;
        }
        root->left=sufficientSubset(root->left,limit-root->val);
        root->right=sufficientSubset(root->right,limit-root->val);
        if(root->left==nullptr&&root->right==nullptr)
            return nullptr;
        return root;
    }
};

3.删点成林

题目来源:1110.删点成林
思路:
出现以下两种情况需要考虑添加节点进forest:

  • 当前节点在delete_set里,则将左右子节点分别添加进forest(前提是子节点不为null),然后设置当前节点为null
  • 当前节点不在delete_set里且父节点为null, 则将当前节点添加进forest

题解:

class Solution {
public:
    vector<TreeNode*> forest;
    unordered_set<int> delete_set;
    TreeNode* deletea(TreeNode* root, TreeNode* parent) {
        if(root==nullptr) return nullptr;
        root->left=deletea(root->left,root);
        root->right=deletea(root->right,root);

        if(delete_set.find(root->val)!=delete_set.end()){
            if(root->left) forest.push_back(root->left);
            if(root->right) forest.push_back(root->right);
            root=nullptr;
        }
        else if(parent==nullptr)
            forest.push_back(root);
        return root;
    }
    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        for(auto val:to_delete) delete_set.insert(val);
        deletea(root,nullptr);
        return forest;
    }
};

4.二叉树展开为链表

题目来源:114.二叉树展开为链表
Tips: C++ Vector 库 - at() 函数:返回对向量中位置 n 处元素的引用
题解:

class Solution {
public:
    void flatten(TreeNode* root) {
        vector<TreeNode*> list;
        dfs(root,list);
        int n=list.size();
        for(int i=1;i<n;i++){
            TreeNode *prev=list.at(i-1),*cur=list.at(i);
            prev->left=nullptr;
            prev->right=cur;
        }
        cout<<endl;
        
    }
    void dfs(TreeNode* root,vector<TreeNode*> &list){
        if(root!=nullptr)
        {
            list.push_back(root);
            dfs(root->left,list);
            dfs(root->right,list);
        }
    }
};

5.分裂二叉树的最大乘积

题目来源:1339.分裂二叉树的最大乘积
题解:

class Solution {
    int sum=0;
    int best=0;
public:
    void dfs(TreeNode* root){
        if(root==nullptr)
            return;
        sum+=root->val;
        dfs(root->left);
        dfs(root->right);
    }
    int dfs2(TreeNode* root){
        if(root==nullptr)
            return 0;
        int cur=dfs2(root->left)+dfs2(root->right)+root->val;
        if(abs(cur*2-sum)<abs(best*2-sum)){
            best=cur;
        }
        return cur;
    }
    int maxProduct(TreeNode* root) {
        dfs(root);
        dfs2(root);
        return (long long)best*(sum-best)%1000000007;
    }
};

6. 二叉树中的最长交错路径

题目来源:1372. 二叉树中的最长交错路径
题解:

class Solution {
    int maxans=0;
public:
    void dfs(TreeNode* root,int dir,int len){
        maxans=max(maxans,len);
        if(dir==0){
            if(root->left) dfs(root->left,1,len+1);
            if(root->right) dfs(root->right,0,1);
        }
        else{
            if(root->right) dfs(root->right,0,len+1);
            if(root->left) dfs(root->left,1,1);
        }
    }
    int longestZigZag(TreeNode* root) {
        if(root==nullptr)
            return 0;
        dfs(root,0,0);
        dfs(root,1,0);
        return maxans;
    }
};

7. 二叉搜索子树的最大键值和

题目来源:1373. 二叉搜索子树的最大键值和
思路:

  • 判断一颗树是否是二叉搜索树的充分条件:
    根节点值大于左子树最大值,说明比左子树所有元素都大
    根节点值小于右子树最小值,说明比右子树所有元素都小
    左子树是二叉搜索树
    右子树是二叉搜索树
    所以我们可以在每层递归里记录四个值:
  • 当前树的最小值
  • 当前树的最大值
  • 当前树的和
  • 当前树是否是二叉搜索树

题解:

class Solution {
    int maxans=0;
public:
    void dfs(TreeNode* root,int dir,int len){
        maxans=max(maxans,len);
        if(dir==0){
            if(root->left) dfs(root->left,1,len+1);
            if(root->right) dfs(root->right,0,1);
        }
        else{
            if(root->right) dfs(root->right,0,len+1);
            if(root->left) dfs(root->left,1,1);
        }
    }
    int longestZigZag(TreeNode* root) {
        if(root==nullptr)
            return 0;
        dfs(root,0,0);
        dfs(root,1,0);
        return maxans;
    }
};

8. 通知所有的员工所需的时间(***没懂)

题目来源:1379.通知所有的员工所需的时间
题解:

class Solution {
public:
    int numOfMinutes(int n, int headID, vector<int>& manager, vector<int>& informTime) {
        int res = 0;
        vector<int> help(n);//记忆化搜索数组 元素为最顶的头到该员工(下标为员工编号)花费的总时间
        function<int(int)> dfs = [&](int sup) {//lambda递归函数
            if (sup == -1) return 0;//到达最顶了 直接返回0
            else if (help[sup]) return help[sup];//如果当前员工在表里,直接取值
            help[sup] = informTime[sup] + dfs(manager[sup]);//返回来的上级总时间+当前员工通知时间即为 最顶员工到当前的总时间
            return help[sup];
        };
        for (int i = 0; i < n; i++) {
            if (informTime[i] != 0) continue;//只从最底层员工开始递归,不是底层员工直接排除
            res = max(res, dfs(manager[i]));//传到当前最底层员工的总时间更新到全局
        }
        return res;
    }
};

9. 收集树上所有苹果的最少时间【***没懂】

题目来源:1443.收集树上所有苹果的最少时间
思路:
把一个树拆分成许多小子树来看,对于一个根节点有三种情况

  1. 根节点无苹果,且其所有的子节点也都无苹果 那么走到该根节点的开销为0,就不用走这个根节点的分支(包括这个根节点)
  2. 根节点不管有没有苹果,只要其子节点当中有苹果 那么走到该根节点的开销为<1>走过所有有苹果的子节点所需要的开销 (继续调用dfs) + <2>上一步走到这个根节点的开销==2
  3. 根节点有苹果,且其子节点无苹果,或者说其子节点为空(该根节点为叶子节点) 那么走到该根节点的开销为上一步走到这个根节点的开销==2

三种情况可以简化为
走到该节点的开销curcost=所有孩子的开销childcost+走到该节点的开销(cost==2)
所有孩子的开销childcost可能=0,表示所有的子节点里都无苹果

  1. 假如所有孩子的开销childcost为0,且该节点本身就没有苹果,那么走到该节点的开销curcost=0
  2. 假如所有孩子开销!=0,不管该节点有没有苹果,走到该节点的开销curcost=childcost+2
  3. 假如所有孩子的开销childcost为0,但是该节点本身有苹果,那么走到该节点的开销curcost=0+2=2

题解:

class Solution {
public:

    //g 图
    //vis 判断节点是否被访问过,遍历过的节点不能再遍历,
    //同时因为是无向图,vis间接的确定了父子节点的关系,在子节点访问其子节点的时候不可能再去访问父节点
    //cost 只有以0节点刚进去的时候cost=0,在之后访问0节点的子节点时,cost都等于2(来回)

    int dfs(int curn, vector<vector<int> >& g, vector<bool>& vis, vector<bool>& hasApple, int cost)
    {
        if(vis[curn])
            return 0;
        vis[curn]=true;
        int childcost=0;
        for(auto next:g[curn])
        {
            childcost+=dfs(next,g,vis,hasApple,2);//遍历当前节点的所有子节点
            //如果childcost=0的话代表所有的子节点都没有苹果
        }

        //对应上面的情况1,所有子节点里都无苹果,且该节点本身也无苹果,走到该节点的开销=0
        if(!hasApple[curn] && childcost==0)
            return 0;

        //对应上面的情况2,3 走到该节点的开销为所有摘子节点里的苹果的开销+走到该节点的开销cost
        //如果childcost=0的话,对应情况3
        //如果childcost!=0的话,对应情况2
        return childcost+cost;
    }


    int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
        vector<vector<int>> g(n);
        vector<bool> vis(n,false);

        for(auto edge:edges)
        {
            g[edge[0]].push_back(edge[1]);
            g[edge[1]].push_back(edge[0]);
        }

        return dfs(0,g,vis,hasApple,0);//第一层由0节点出发,开销是0
    }

};

10. 二叉树中的伪回文路径

题目来源:1457.二叉树中的伪回文路径
题解:

class Solution {
public:
    int count=0;
    //检查是否为回文序列
    void check(unordered_map<int,int> &path){
        int flag=0;
        for(unordered_map<int,int>::iterator it=path.begin();it!=path.end();it++)
        {
            if(it->second%2==1) flag++;
        }
        if(flag<=1) count++;
    }
    void dfs(TreeNode* root,unordered_map<int,int> &path)
    {
        if(root==nullptr)   return;
        path[root->val]++;
        if(root->left==nullptr&&root->right==nullptr)
            check(path);
        dfs(root->left,path);
        dfs(root->right,path);
        path[root->val]--;
    }
    int pseudoPalindromicPaths (TreeNode* root) {
        unordered_map<int,int> path;
        dfs(root,path);
        return count;
    }
};

11. 统计最高分的节点数目【*****】

题目来源:2049.统计最高分的节点数目
题解:

class Solution {
public:
    long maxScore = 0;  //记录最大分数,用于cnt++
    int cnt = 0;    //返回值,即最高得分节点数目
    int n;  //节点总数,用于得到size用于计算三种情况下节点的个数
    vector<vector<int>> children;   //构图,用于dfs

    int dfs(int node) {
        long score = 1;
        int size = n - 1;   //当前节点被删除
        for (int c : children[node]) {
            int t = dfs(c); //得到以当前节点孩子节点为根节点的树的节点个数
            score *= t; //乘左右孩子数量
            size -= t;  //保存剩余节点个数
        }
        //判断是否为最初根节点,否则导致size=0
        if (node != 0) {
            score *= size;  //乘被删节点父节点所在树节点的节点个数
        }
        
        //代表出现了与最高分相同的节点,返回答案计数+1
        if (score == maxScore) {
            cnt++;
        } else if (score > maxScore) {
            maxScore = score;
            cnt = 1;
        }
        return n - size;    //返回以当前节点node为根节点组成的树的节点总数
    }

    int countHighestScoreNodes(vector<int>& parents) {
        this->n = parents.size();
        //[父亲]:{左右孩子}
        this->children = vector<vector<int>>(n);
        for (int i = 0; i < n; i++) {
            int p = parents[i];
            if (p != -1) {
                children[p].emplace_back(i);
            }
        }
        //从根节点开始dfs搜索
        dfs(0);
        //返回有最高得分节点的数目
        return cnt;
    }
};

12. 所有可能的真二叉树

题目来源:894. 所有可能的真二叉树
题解:

/**
 * 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<TreeNode*> allPossibleFBT(int n) {
        vector<TreeNode*> dp;
        if(n%2==0) return dp;
        if(n==1) {
            dp.push_back(new TreeNode(0));
            return dp;
        }
        for(int i=1;i<=n-2;i++){
            vector<TreeNode*> left=allPossibleFBT(i);
            vector<TreeNode*> right=allPossibleFBT(n-1-i);
            for(int j=0;j<left.size();++j){
                for(int k=0;k<right.size();++k){
                    TreeNode *root = new TreeNode(0);
                    root->left = left[j];
                    root->right = right[k];
                    //对于左子树有i个结点,右子树有N-1-i个结点时,我们把所有可能的树push进入队列
                    dp.push_back(root);
                }
            }
        }
        return dp;
    }
};

你可能感兴趣的:(leetcode,leetcode,算法,力扣,数据结构)