6.二叉树.题目1

6.二叉树.题目

题目

1.翻转二叉树

(题目链接)

直观的思路是就把每一个节点的左右孩子交换一下就可以了,
深度优先-递归法

前序,后序遍历方法都没有问题,但中序的递归法会存在问题。

深度优先-迭代法

在迭代法-统一写法下,前序,后序遍历方法都没有问题,就是在while循环else接口处设置std::swap()函数交换左右子节点的位置。但是唯独中序遍历不方便,因为中序遍历会把某些节点的左右子节点翻转了两次 。解释:因为中序遍历的特殊点:不同于前序,后序在遍历过程中在一开始,或结尾处交换左右子节点的位置,而中序在交换左,右子节点中间调换了左右子节点,所以第二个递归参数应该是现在的左子树才是原来的右子树。

cpp 复制代码
    TreeNode* invertTree(TreeNode* root) {
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                swap(node->left, node->right);          // 节点处理逻辑
            }
        }
        return root;
    }

广度优先-迭代法

在层序遍历的基础上,对每个queue.top()节点使用设置std::swap()函数交换左右子节点的位置。

cpp 复制代码
    TreeNode* invertTree(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                swap(node->left, node->right); // 节点处理
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return root;
    }

2.对称二叉树

(题目链接)

给定一个二叉树,检查它是否是镜像对称的。对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。

递归法

除了判断子树的节点数值是否相等,还得处理节点为nullptr的情况

  • 左节点为空,右节点不为空,不对称,return false
  • 左节点不为空,右节点为空,不对称 return false
  • 左右节点都为空,对称,返回true

然后就可以单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况

  • 比较二叉树外侧是否对称:传入的是左节点的左节点,右节点的右节点。
  • 比较内侧是否对称,传入左节点的右节点,右节点的左节点。
  • 如果左右节点都对称就返回true ,有一侧不对称就返回false,使用与逻辑运算符 。
cpp 复制代码
    bool compare(TreeNode* left, TreeNode* right){
    	// 处理不同左右节点的情况的返回值
        if(left == nullptr && right != nullptr) return false;
        else if(left != nullptr && right == nullptr) return false;
        else if(left == nullptr && right == nullptr) return true;
        else if(left->val != right->val) return false;

        bool outside = compare(left->left, right->right);
        bool intside = compare(left->right, right->left);
        bool isame = outside && intside;
        return isame;
    }

    bool isSymmetric(TreeNode* root) {
        if(root==nullptr) return true;
        return compare(root->left, root->right);
    }

深度优先-迭代法

迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的,主要是比较的子节点的逻辑不错:left->leftright->rightleft->rightright->left进行比较,使用队列queue,堆stack都能实现该功能。

cpp 复制代码
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        queue<TreeNode*> que;
        que.push(root->left);   // 将左子树头结点加入队列
        que.push(root->right);  // 将右子树头结点加入队列
        
        while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转
            TreeNode* leftNode = que.front(); que.pop();
            TreeNode* rightNode = que.front(); que.pop();
            if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的
                continue;
            }

            // 左右一个节点不为空,或者都不为空但数值不相同,返回false
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            que.push(leftNode->left);   // 加入左节点左孩子
            que.push(rightNode->right); // 加入右节点右孩子
            que.push(leftNode->right);  // 加入左节点右孩子
            que.push(rightNode->left);  // 加入右节点左孩子
        }
        return true;
    }

3.二叉树的最大深度

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)

这题在二叉树的层序遍历的求二叉树最大深度里提及到了。
深度优先-递归法

cpp 复制代码
    int count_depth(TreeNode* root){
        if(root == nullptr) return 0;
        int leftdepth = count_depth(root->right);
        int rightdepth = count_depth(root->left);
        int depth = 1 + max(leftdepth, rightdepth);
        return depth;
    }

    int maxDepth(TreeNode* root) {
        return count_depth(root);
    }

本题当然也可以使用前序,代码如下:(充分表现出求深度回溯的过程)。由全局变量result记录遍历到的最大深度,最后返回该result

cpp 复制代码
    int result;
    void getdepth(TreeNode* node, int depth) {
        result = depth > result ? depth : result; // 中

        if (node->left == NULL && node->right == NULL) return ;

        if (node->left) { // 左
            depth++;    // 深度+1
            getdepth(node->left, depth);
            depth--;    // 回溯,深度-1
        }
        if (node->right) { // 右
            depth++;    // 深度+1
            getdepth(node->right, depth);
            depth--;    // 回溯,深度-1
        }
        return ;
    }
    int maxDepth(TreeNode* root) {
        result = 0;
        if (root == NULL) return result;
        getdepth(root, 1);
        return result;
    }

4.二叉树的最小深度

(题目链接)

这与求最大深度有点不同,最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点,叶子节点是没有子节点的节点。

递归法

cpp 复制代码
    int getDepth(TreeNode* node) {
        if (node == NULL) return 0;
        int leftDepth = getDepth(node->left);           // 左
        int rightDepth = getDepth(node->right);         // 右
                                                        // 中
        // 当一个左子树为空,右不为空,这时并不是最低点
        if (node->left == NULL && node->right != NULL) { 
            return 1 + rightDepth;
        }   
        // 当一个右子树为空,左不为空,这时并不是最低点
        if (node->left != NULL && node->right == NULL) { 
            return 1 + leftDepth;
        }
        int result = 1 + min(leftDepth, rightDepth);
        return result;
    }

因此需要在求最大深度的代码种,除了将max改为min,还需要加入两个if的判断,以返回其单子节点的情况。

层序遍历-迭代法

层序遍历,则较为简单,只要遍历到该节点的左右子节点为空时,返回此时的深度即可。


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

题目链接
对于普通的二叉树
递归法

求左子节点的数量,求右子节点的数量,最后把两者取和+1,就得到该节点(包含自己)的节点数量。

cpp 复制代码
    int getNodesNum(TreeNode* cur) {
        if (cur == NULL) return 0;
        int leftNum = getNodesNum(cur->left);      // 左
        int rightNum = getNodesNum(cur->right);    // 右
        int treeNum = leftNum + rightNum + 1;      // 中
        return treeNum;
    }

此时时间复杂度是:O(n),空间复杂度是O(log n)。

对于深度优先,广度优先-迭代法,只要在遍历到的节点时,令变量result+1即可。

对于完全二叉树-特殊性质

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。并且完全二叉树只有两种情况,情况一:就是满二叉树;情况二:最后一层叶子节点没有满。

  1. 如何计算子节点的数量?可以通过判断其子树是不是满二叉树,如果是则利用==公式(2^depth-1)==计算这个子树(满二叉树)的节点数量,如果不是则继续递归
  2. 如何判断是否是满二叉树?在完全二叉树的条件下,若向左遍历深度等于向右遍历深度,则该二叉树是满二叉树。
cpp 复制代码
    int countNodes(TreeNode* root) {
    	// 终止条件写法
        if (root == nullptr) return 0;
        TreeNode* left = root->left;
        TreeNode* right = root->right;
        int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
        while (left) {  // 求左子树深度
            left = left->left;
            leftDepth++;
        }
        while (right) { // 求右子树深度
            right = right->right;
            rightDepth++;
        }
        // 满二叉树的条件
        if (leftDepth == rightDepth) {
            return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        }
        // 单层递归逻辑-可以看出使用了后序遍历
		int leftTreeNum = countNodes(root->left);       // 左
		int rightTreeNum = countNodes(root->right);     // 右
		int result = leftTreeNum + rightTreeNum + 1;    // 中
		return result;
    }

此时时间复杂度是:O(log n × log n),空间复杂度是O(log n)。


6.平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。

虽然已经提及好几次,但要注意高度和深度的区别。在维基上以边为一度,但leetcode上以节点为一度,笔记以leetcode为准。

使用后序遍历符合求解二叉树节点的高度相关的问题(另外根节点的高度就是这棵树的最大深度)。而前序遍历是求二叉树节点深度相关的问题。终止条件依然是节点是否为空;单层递归逻辑是,分别求左右子节点的高度,并判断两者高度差,若高度差小于等于1,返回该节点的最大高度,若高度差大于1则返回其高度为-1(正常而言节点的高度>0),这样就会使上层的节点高度返回值均大于1,最后返回-1,以-1为标志位。
递归法

cpp 复制代码
    int geteHeight(TreeNode* root){
    	// 终止条件
        if(root==nullptr) return 0;
        // 单层递归逻辑
        int leftH = geteHeight(root->left);
        if(leftH==-1) return -1;
        int rihgtH = geteHeight(root->right);
        if(rihgtH==-1) return -1;
        return abs(leftH-rihgtH)>1? -1:1+max(leftH, rihgtH);
    }

    bool isBalanced(TreeNode* root) {
        return geteHeight(root) == -1? false : true;
    }

迭代法

需要预拟定一个函数-求节点高度(后序遍历);然后在前序遍历中,找每一个节点的高度,对比左右子节点的高度差是否满足条件即可。


7.二叉树的所有路径

题目链接

给定一个二叉树,返回所有从根节点到叶子节点的路径。因此这类似根节点高度的求法,只是终止条件改变为了该节点的左右子节点是否为空,题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
递归法

在使用递归法的时候,切记回溯和递归是一一对应的,有一个递归,就要有一个回溯。

cpp 复制代码
    void traversal(TreeNode* root, std::vector<int>& path, std::vector<std::string>& result){
        path.push_back(root->val); //path添加当前节点值
        if(root->left==nullptr && root->right==nullptr){
            std::string sPath;
            for(int i=0; i<path.size()-1; i++){
                sPath += to_string(path[i]);
                sPath += "->";
            }
            sPath += to_string(path[path.size()-1]);
            result.push_back(sPath);
            return;
        }
        // 单层递归逻辑
        if(root->left){
            traversal(root->left, path, result);
            path.pop_back(); //回溯
        }
        if(root->right){
            traversal(root->right, path, result);
            path.pop_back(); //回溯
        }
    }

    vector<string> binaryTreePaths(TreeNode* root) {
        std::vector<std::string> result;
        std::vector<int> path;
        if(root==nullptr) return result;
        traversal(root, path, result);
        return result;
    }

8.左叶子之和

题目链接

这道题目要求左叶子之和,其实是比较绕的,因为不能判断本节点是不是左叶子节点,需要通过通过节点的父节点判断本节点的属性。因此需使用后序遍历的方法。
递归法

cpp 复制代码
    int sumOfLeftLeaves(TreeNode* root) {
        if(root == nullptr) return 0;
        if(root->left==nullptr && root->right==nullptr) return 0;
        int leftvalue = sumOfLeftLeaves(root->left);
        int rightval = sumOfLeftLeaves(root->right);
        if(root->left && !root->left->left && !root->left->right){
            leftvalue = root->left->val;
        }
        int sum = leftvalue + rightval;
        return sum;
    }

迭代法

cpp 复制代码
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        if (root == NULL) return 0;
        st.push(root);
        int result = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
                result += node->left->val;
            }
            if (node->right) st.push(node->right);
            if (node->left) st.push(node->left);
        }
        return result;
    }

总结

  1. 递归函数什么时候需要返回值?什么时候不需要返回值?以下总结三点:

    • 如果需要搜索二叉树且不用处理递归返回值,递归函数就不要返回值
    • 如果需要搜索二叉树且需要处理递归返回值,递归函数就需要返回值。
    • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径就要及时返回。
  2. 思考如何根据两个顺序构造唯一一个二叉树

    • 以后序数组的最后一个元素为切割点,先切中序数组,根据所切割的左中序数组大小作为切割点,反过来再切后序数组
    • 一层一层地切下去,每次后序数组最后一个元素就是节点元素
      因为后序数组的最后一个元素必为最深的父节点,而中序数组中相邻的元素会被父节点所切割
  3. 构造树一般采用前序遍历,因为先构造中间节点,再构造左,右子节点。

相关推荐
依晴无旧2 分钟前
数组算法(二):交替子数组计数
数据结构·算法
GSDjisidi14 分钟前
日本IT-SIER/SES的区别详情、契约形态等
java·大数据·c语言·c++·php
YoungMLet20 分钟前
【QT】多元素控件
c语言·开发语言·c++·qt·ui
振华首席娱记1 小时前
代码随想录——划分字母区间(Leetcode763)
java·数据结构·算法·leetcode·职场和发展
阳光男孩011 小时前
力扣3026.最大好子数组和
数据结构·算法·leetcode
paidaxing_s1 小时前
【FFMPEG基础(一)】解码源码
linux·c++·ffmpeg
曼巴UE51 小时前
UE C++ 多镜头设置缩放 平移
开发语言·c++
悄悄敲敲敲1 小时前
栈的实现详解
c语言·开发语言·数据结构·c++·算法·链表·线性回归
danaaaa2 小时前
算法力扣刷题总结篇 ——【四】
数据结构·c++·算法·leetcode·职场和发展
安於宿命2 小时前
0/1背包问题总结
c语言·c++·算法·leetcode·动态规划