数据结构与算法之二叉树: LeetCode 101. 对称二叉树 (Typescript版)

对称二叉树

  • https://leetcode.cn/problems/symmetric-tree/

描述

  • 给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1

       1
    /  |  \
   2   |   2
  / \  |  / \
 3   4 | 4   3

中间一条线是对称轴

输入:root = [1,2,2,3,4,4,3]
输出:true

示例 2

  1
 /  \
2    2
 \    \
  3    3
输入:root = [1,2,2,null,3,null,3]
输出:false

提示

  • 树中节点数目在范围 [1, 1000] 内
  • -100 <= Node.val <= 100

算法实现

1 )深度优先递归版本

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function isSymmetric(root: TreeNode | null): boolean {
    if(!root) return true;
    const isMirror = (l: TreeNode | null, r: TreeNode | null) => {
        if(!l && !r) return true; // 空树是镜像的
        return l?.val === r?.val && isMirror(l?.left, r?.right) && isMirror(r?.left, l?.right)
    };
    return isMirror(root.left, root.right);
}
  • 解题思路
    • 转化为:左右子树是否镜像
    • 分解为:树1的左子树和树2的左子树是否镜像;树1的右子树和树2的左子树是否镜像,成立,且根节点值相同,则镜像
    • 符合:分、解、合的特性,考虑分而治之
  • 解题步骤
    • 分:获取两个树的左子树和右子树
    • 解:递归地判断树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像
    • 合:如果上述两个都成立,且根节点值也相同,两个树就是镜像
  • 时间复杂度:O(n)
    • 叶子节点数量
  • 空间复杂度: O(n)
    • 堆栈的高度,树的高度
    • 最坏情况高度==树节点数

2 )广度优先迭代版本

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function isSymmetric(root: TreeNode | null): boolean {
    if(!root || (!root.left && !root.right)) return true;
    const queue: (TreeNode | null)[] = [root.left, root.right];
    while(queue.length) {
        const left = queue.shift();
        const right = queue.shift();
        // 如果两个节点都为空就继续循环,两者有一个为空就返回false
        if (!left && !right) continue;
        if (!left || !right) return false;
        if (left.val !== right.val) return false;
        // 将左节点的左孩子, 右节点的右孩子 存入队列
        queue.push(left.left);
        queue.push(right.right);
        // 将左节点的右孩子 和 右节点的左孩子 存入队列
        queue.push(left.right);
        queue.push(right.left);
    }
    return true;
}
  • 使用广度优先遍历来替代深度优先遍历实现对称校验
  • 时间复杂度是 O(n)
  • 空间复杂度是 O(n)

扩展

二叉树中,从数组中的数据解析成Tree Node 结构的过程如下

1 )二叉树节点和数组索引(下标)之间的关系,如下

0层         0           2的0次方 = 1 个
         /     \
1层     1        2      2的1次方 = 2 个
       / \      / \
2层   3    4   5    6   2的2次方 = 4 个

2 )从数组转换成Tree Node的代码实现过程如下,如下


class TreeNode {
    val: number
    left: TreeNode | null
    right: TreeNode | null
    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
        this.val = (val === undefined ? 0 : val)
        this.left = (left === undefined ? null : left)
        this.right = (right === undefined ? null : right)
    }
}

class Tree {
  constructor (data) {
    // 临时存储所有节点,方便寻找父子节点
    const nodeList = [];
    for (let i = 0, len = data.length; i < len; i++) {
      const node = new TreeNode(data[i]);
      nodeList.push(node);
      if (i === 0) continue; // 开始层root跳过处理
      // 计算当前节点属于那一层
      const n = Math.floor(Math.sqrt(i + 1));
      // 记录当前层的起始点
      const q = Math.pow(2, n) - 1;
      // 记录上一层的起始点
      const p = Math.pow(2, n - 1) - 1;
      // 找到当前节点的父节点
      const parent = nodeList[p + Math.floor((i - q) / 2)];
      // 将当前节点和上一层的父节点做关联
      if (parent.left) {
        parent.right = node
      } else {
        parent.left = node
      }
    }
    const root = nodeList.shift(); // 拿出第一个节点
    nodeList.length = 0; // 清空临时节点列表
    return root;
  }
}
  • 从上述关系和代码过程可以看到
    • 当前节点(的下标i)与层级n的关系: const n = Math.floor(Math.sqrt(i + 1))
    • 当前层的起点下标: const q = Math.pow(2, n) - 1
    • 上一层的起点下标: const p = Math.pow(2, n - 1) - 1
    • 基于当前节点,找到其父节点: const parent = nodeList[p + Math.floor((i - q) / 2)]
  • 我们如果想到得到从数组转换过来的当前树,只需要执行 const root = new Tree([1, 2, 2, 3, 4, 5, 3])
    • 第一个数组参数是传入的数据
    • 得到的 root 是这棵树的起点,内部已经构建好了节点之间的关系
    • 当然,这只是从数组转二叉树的一种广度优先遍历的实现形式

你可能感兴趣的:(Data,Structure,and,Algorithms,leetcode,算法,二叉树)