[日记]LeetCode算法·五——二叉树②

1 完全二叉树的节点个数

LeetCode:完全二叉树的节点个数

①遍历法

无需多说,利用左右子树的个数之和+1即可,标准的后序遍历思路。当然也能用层序遍历,但不必多写。

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(root==nullptr)return 0;
        return 1+countNodes(root->left)+countNodes(root->right);
    }
};

②满二叉树

显然,完全二叉树是由若干个满二叉树组成的,而满二叉树的个数可以简单的使用2^depth-1进行计算。
因此最简单的思路是判断最左节点和最右节点的深度是否一致,一致说明为满二叉树,不一致则递归左右子树。

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(root==nullptr)return 0;
        int left_depth=0;
        int right_depth=0;

        TreeNode* cur=root;
        while(cur->left)
        {
            cur=cur->left;
            ++left_depth;
        }

        cur=root;
        while(cur->right)
        {
            cur=cur->right;
            ++right_depth;
        }

        if(left_depth==right_depth)return (2<<(left_depth))-1;
        else return 1+countNodes(root->left)+countNodes(root->right);
    }
};

2 平衡二叉树

LeetCode:平衡二叉树

依然是后序遍历的思路,判断左右子树的高度是否相差超过1,利用-1作为标记可以从底层进行判断。

class Solution {
public:
    int isBalancedAssist(TreeNode* root)
    {
        //终止条件
        if(root==nullptr)return 0;
        if(root->left==nullptr && root->right==nullptr)return 1;

        int left_result=isBalancedAssist(root->left);
        int right_result=isBalancedAssist(root->right);

        if(left_result==-1 || right_result==-1 || left_result-right_result>1 || right_result-left_result>1)
            return -1;
        else return 1+max(left_result,right_result);
    }
    bool isBalanced(TreeNode* root) {
        if(root==nullptr)return true;
        return isBalancedAssist(root)!=-1;
    }
};

3 二叉树的所有路径

LeetCode:二叉树的所有路径

依然是后序遍历的思路,路径即根节点->左子树路径+根节点->右子树路径,理论上利用栈和回溯也可以实现。

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<string> left_result;
        vector<string> right_result;
        if(root==nullptr)return result;
        if(root->left==nullptr&&root->right==nullptr)
            result.push_back(to_string(root->val));
        else
        {
            if(root->left)
                left_result=binaryTreePaths(root->left);
            if(root->right)
                right_result=binaryTreePaths(root->right);
            
            for(int i=0;i<left_result.size();i++)
            {
                result.push_back(to_string(root->val)+"->"+left_result[i]);
            }
            for(int i=0;i<right_result.size();i++)
            {
                result.push_back(to_string(root->val)+"->"+right_result[i]);
            }
        }
        return result;
    }
};

4 左叶子之和

LeetCode:左叶子之和

层序遍历或后序遍历都可以处理,给出后序遍历处理代码。

class Solution {
public:
    int sumOfLeftLeavesAssist(TreeNode* root,bool isLeft)
    {
        if(root==nullptr)return 0;
        //左叶子
        if(root->left==nullptr && root->right==nullptr && isLeft)
            return root->val;
        //左子树左叶子之和
        int left_sum=sumOfLeftLeavesAssist(root->left,true);
        //右子树左叶子之和
        int right_sum=sumOfLeftLeavesAssist(root->right,false);
        //后序处理
        return left_sum+right_sum;
    }
    int sumOfLeftLeaves(TreeNode* root) {
        return sumOfLeftLeavesAssist(root,false);
    }
};

5 找树左下角的值

LeetCode:找树左下角的值

层序遍历是最容易想到的,这里采用递归+回溯进行处理。

class Solution {
public:
    int maxDepth=-1;
    int result;

    void traversal(TreeNode* root,int depth)
    {
        if(root->left==nullptr && root->right==nullptr)
        {
            result = depth > maxDepth ? root->val : result;
            maxDepth = depth > maxDepth ? depth : maxDepth;
        }

        if(root->left)
        {
            ++depth;
            traversal(root->left , depth);
            --depth;
        }
        if(root->right)
        {
            ++depth;
            traversal(root->right,depth);
            depth--;
        }
    }
    int findBottomLeftValue(TreeNode* root) {
        //搜到最深处的最左节点
        //递归+回溯思想
        TreeNode* cur=root;
        traversal(root,0);
        return result;
    }
};

6 路径总和

LeetCode:路径总和

这题最简单明了的思路必然是递归,将target进行缩小即可。

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        //特殊情况处理
        if(root==nullptr)return false;
        //递归终止条件
        if(root->left==nullptr && root->right==nullptr && root->val==targetSum)return true;
        if(root->left==nullptr && root->right==nullptr && root->val!=targetSum)return false;
        
        if(root->left==nullptr && root->right)
            return hasPathSum(root->right,targetSum-root->val);
        if(root->left && root->right==nullptr)
            return hasPathSum(root->left,targetSum-root->val);
        
        return hasPathSum(root->left,targetSum-root->val)||hasPathSum(root->right,targetSum-root->val);
    }
};

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

LeetCode:从中序与后序遍历序列构造二叉树

中序遍历+(前序遍历/后序遍历)可以确定一颗元素不重复的二叉树,因为任何遍历左子树元素必然在右子树元素之前,而根节点必然在后序遍历最后或前序遍历最前,因此可以利用根节点和中序遍历,确定左右子树的元素,从而迭代构建二叉树。
以下两种分别代表着容易理解和节约空间的两种实现,即对于子树的遍历结果的管理方式不同。

class Solution {
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        //后序遍历的最后一个一定是根节点
        //在中序遍历中找到根节点,其左侧为左子树元素,其右侧为右子树元素
        //后序遍历中也一样,左子树元素在先,右子树元素在后,跟着中序遍历进行寻找即可
        //递归进行build,直到遇到叶节点结束
        
        //建立根节点
        int root_val=postorder[postorder.size()-1];
        TreeNode* root=new TreeNode(root_val);
        //递归终止条件
        if(inorder.size()==1)return root;
        //判断左右子树的标志
        bool left=true;
        //存放左子树和右子树的中序与后序遍历
        vector<int> left_in,left_post,right_in,right_post;
        //循环存放遍历结果
        for(int i=0;i<inorder.size();++i)
        {
            if(inorder[i]==root_val)
            {
                //接下来都是右子树
                left=false;
                continue;
            }
            //左子树元素
            if(left)
            {
                left_in.push_back(inorder[i]);
                left_post.push_back(postorder[i]);
            }
            //右子树元素
            else
            {
                right_in.push_back(inorder[i]);
                right_post.push_back(postorder[i-1]);
            }
        }
        //左右节点连接
        if(left_in.size()!=0)root->left=buildTree(left_in,left_post);
        if(right_in.size()!=0)root->right=buildTree(right_in,right_post);
        return root;
    }
};
class Solution {
public:
    TreeNode* buildTreeAssist(vector<int>& inorder, vector<int>& postorder,int in_left,int in_right,
                                int post_left,int post_right)
    {
        int root_val=postorder[post_right];
        TreeNode* root=new TreeNode(root_val);
        if(in_left==in_right)return root;

        int new_in_mid,new_post_mid;
        for(int i=0;i<=in_right-in_left;i++)
        {
            if(inorder[in_left+i]==root_val)
            {
                new_in_mid=in_left+i-1;
                new_post_mid=post_left+i-1;
                break;
            }
        }
        if(new_in_mid>=in_left)
            root->left=buildTreeAssist(inorder,postorder,in_left,new_in_mid,post_left,new_post_mid);
        if(new_in_mid+2<=in_right)
            root->right=buildTreeAssist(inorder,postorder,new_in_mid+2,in_right,new_post_mid+1,post_right-1);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        return buildTreeAssist(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
    }
};

8 最大二叉树

LeetCode:最大二叉树

和上一道中序遍历+后序遍历构建二叉树没啥本质区别,唯一的区别在于根节点的确定使用最大值,而不是由后序遍历来确定。

class Solution {
public:
    int max(vector<int>& nums)
    {
        int max_val=nums[0];
        for(int i=1;i<nums.size();i++)
        {
            if(nums[i]>max_val)max_val=nums[i];
        }
        return max_val;
    }
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        int root_val=max(nums);
        TreeNode* root=new TreeNode(root_val);
        if(nums.size()==1)
            return root;
        
        vector<int> left_nums,right_nums;
        bool left=true;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]==root_val)
            {
                left=false;
                continue;
            }
            if(left)left_nums.push_back(nums[i]);
            else right_nums.push_back(nums[i]);
        }
        if(left_nums.size())root->left=constructMaximumBinaryTree(left_nums);
        if(right_nums.size())root->right=constructMaximumBinaryTree(right_nums);
        return root;
    }
};

9 合并二叉树

LeetCode:合并二叉树

递归法是最简单明了的,利用前序遍历思想,对根节点处理完后,对左右子树进行递归。
另外采用队列进行层序遍历也同样可以完成,只是需要注意节点赋值和条件判断之间的顺序不能随意打乱。


①递归(前序遍历)

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        //递归终止
        if(root1==nullptr)return root2;
        if(root2==nullptr)return root1;

        //均非空
        //前序遍历
        root1->val=root1->val+root2->val;
        root1->left=mergeTrees(root1->left,root2->left);
        root1->right=mergeTrees(root1->right,root2->right);
        return root1;
    }
};

①队列(层序遍历)

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(root1==nullptr)return root2;
        if(root2==nullptr)return root1;

        queue<TreeNode*> que;
        que.push(root1);
        que.push(root2);

        TreeNode* cur1;
        TreeNode* cur2;

        while(!que.empty())
        {
            cur1=que.front();
            que.pop();
            cur2=que.front();
            que.pop();

            //中
            cur1->val+=cur2->val;
            //左
            if(cur1->left && cur2->left)
            {
                que.push(cur1->left);
                que.push(cur2->left);
            }
            else {if(cur1->left==nullptr)cur1->left=cur2->left;}
            //右
            if(cur1->right && cur2->right)
            {
                que.push(cur1->right);
                que.push(cur2->right);
            }
            else {if(cur1->right==nullptr)cur1->right=cur2->right;}
        }
        return root1;
    }
};

10 总结

普通二叉树的题目已经基本上做完了,找到规律实际上部分容易。绝大数情况下,DFS的前中后序遍历+递归是最好且最简单的实现思路,而迭代法与回溯也在做题中慢慢找到一些感觉了。
还是选择屈服算了,不再对每一个段落进行缩进,直接进行一个烂的摆,MarkDown成功驯化我了。另外马上就要开学了,接下来应该是对二叉搜索树、AVL树、B树以及最小顶堆进行一些复习和练习。
在学业方面,打算在GAN、Diffusion以及NERF方面进行一些钻研吧。

——2023.2.19

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值