代码随想录算法训练营第十四天|二叉树part2

120 阅读5分钟

层序遍历

可以借助队列来实现

  • 树每层的元素进入队列,开始遍历,逐个弹出
  • 每个元素弹出后,就把其左右子树加入队列中
  • 每次需要统计size,即每次需要弹出的元素的个数
var levelOrder = function(root) {
    //二叉树的层序遍历
    let res = [], queue = [];
    queue.push(root);
    if(root === null) {
        return res;
    }
    while(queue.length !== 0) {
        // 记录当前层级节点数,方便之后以此弹出
        let length = queue.length;
        //存放每一层的节点
        let curLevel = [];
        for(let i = 0;i < length; i++) {
            let node = queue.shift();
            // 存放当前弹出的节点
            curLevel.push(node.val);
            // 存放当前层下一层的节点
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        //把每一层的结果放到结果数组
        res.push(curLevel);
    }
    return res;
};

记住这个模板,分几步做

  • 统计当前层的节点数
  • 遍历,节点进入队列,再依次出队
  • 每个节点出队后都将其左右子节点入队
  • 循环,直到队列为空(到了最后一行)

226.翻转二叉树

题目链接:226. 翻转二叉树 - 力扣(LeetCode)

思路

层序遍历,原理和上面的是一样的

在遍历每层的过程中

先把节点的左右子节点互换,再将左右子节点依次入队

var invertTree = function(root) {
     let queue = [];
    queue.push(root);
    if(root === null) {
        return root;
    }
    while(queue.length !== 0) {
        // 记录当前层级节点数,方便之后以此弹出
        let length = queue.length;
        //存放每一层的节点
        let curLevel = [];
        for(let i = 0;i < length; i++) {
            // 当前节点出栈
            let node = queue.shift();
            // 互换左右节点
              if(node.left || node.right){
                let i = node.left;
                node.left = node.right;
                node.right = i;
            }
            
            // 存放当前层下一层的节点
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        //把每一层的结果放到结果数组
        ;
    }
    return root;
};

另外,本题还可以用深度优先遍历的方法,即前序/中序遍历

深度优先遍历主要是用递归法

递归法的逻辑和上面是一样的,这里把交换节点的逻辑放在 来执行

注意

这里前序和后序都比较合适,但是中序遍历不合适

因为,前序是中左右,中序是左中右,后序是左右中

这里的逻辑是把其左右子节点互换位置,所以中只能在两边而不能在中间

这里画个图可以理解:

因此,代码如下:

  • 前序:

    var invertTree = function(root) {
        function dfs(root) {
            if(root === null){
                return root;
            }
            swap(root, root.left,root.right)
            dfs(root.left);
            dfs(root.right);
        }
        dfs(root);
        function swap(root, l, r){
            let i = l;
            l = r;
            r = i;
            root.left = l;
            root.right = r;
    ​
        }
        return root;
    };
    
  • 后序

    要注意的点主要在于递归的单层逻辑怎么处理

    var invertTree = function(root) {
        function dfs(root) {
            if(root === null){
                return root;
            }
           // 这里把递归的顺序换一下
            dfs(root.left);
            dfs(root.right);
            swap(root, root.left,root.right)
        }
        dfs(root);
        function swap(root, l, r){
            let i = l;
            l = r;
            r = i;
            root.left = l;
            root.right = r;
    ​
        }
        return root;
    };
    

总结

这题既可以用层次遍历,也可以用深度优先遍历

基本都是在模板上进行修改即可

思路需要想清楚

101. 对称二叉树

题目链接:101. 对称二叉树 - 力扣(LeetCode)

第一想法

层序遍历

遍历到每一层,对其两个节点做一个判断函数

函数作用为对比两个节点的左右子节点是否对称

思路

上面的想法有一点问题,比如碰到这种情况

image-20230330142309621

画起来的两个并不属于同一个元素的左右子节点

这里可以用递归法:

对根节点的两个子树进行遍历

其一遍历方式为左右中

另一个的遍历方式为右左中

如果遍历结果相等,则对称

递归三部曲:

  • 确定参数和返回值:

    参数是左右节点,返回值是bool类型

  • 确定终止条件

    左节点为空,右不空,返回false

    左不空,右空,返回false

    左空右空,返回true

    左右都不空但是也不相等,返回false

  • 确定单层递归逻辑

    比较外侧,左节点的左孩子和右节点的右孩子

    比较内侧,右节点的左孩子和左节点的右孩子

    如果两侧都是true,则返回true

代码如下:

var isSymmetric = function(root) {
   if(root === null) return false;
   return compare(root.left, root.right);
​
};
 function compare(l,r){
        if(l === null && r === null) return true;
        else if(l !== null && r === null) return false;
         else if(r !== null && l === null) return false;
         else if(l.val !== r.val) return false;
         else return compare(l.left,r.right) && compare(l.right, r.left);
    }

也可以用层序遍历的方法

入队的逻辑是:

左节点左孩子入队

右节点右孩子入队

左节点右孩子入队

右节点左孩子入队

出队的逻辑是:

每次队伍最前面的两个元素出队

判断是否相等,如果不等就可以直接return false

代码如下:

var isSymmetric = function(root) {
  // 迭代方法判断是否是对称二叉树
  // 首先判断root是否为空
  if(root === null) {
      return true;
  }
  let queue = [];
  queue.push(root.left);
  queue.push(root.right);
  while(queue.length) {
      let leftNode = queue.shift();    //左节点
      let rightNode = queue.shift();   //右节点
      // 这一步是当遍历到底层时,就不需要再有孩子入队了,所以continue
      if(leftNode === null && rightNode === null) {
          continue;
      }
      if(leftNode === null || rightNode === null || leftNode.val !== rightNode.val) {
          return false;
      }
      queue.push(leftNode.left);     //左节点左孩子入队
      queue.push(rightNode.right);   //右节点右孩子入队
      queue.push(leftNode.right);    //左节点右孩子入队
      queue.push(rightNode.left);    //右节点左孩子入队
  }
  
  return true;
};

这里的入队逻辑和之前的不一样,需要注意

以及,虽然每次入队的是4个,出队的是两个

但是有这一步:

if(leftNode === null && rightNode === null) {
          continue;
      }

保证了可以所有孩子入队并且按顺序依次出队

总结

这一题同时可以用深度优先和层序遍历

在套模板的基础上

需要去好好思考一下解题的思路