# 二叉树

# 遍历系列

# 二叉树的前序遍历 (opens new window)

// 递归
function pre(root: TreeNode): void {
  if(!root) return
  console.log(root.val)
  pre(root.left)
  pre(root.right)
}

// 迭代
function pre(root: TreeNode): number[] {
  if(!root) return []
  const res: number[] = []
  const stack: TreeNode = [root]
  
  while(stack.length) {
    const node = stack.pop()
    res.push(node.val)
    node.right && stack.push(node.right)
    node.left && stack.push(node.left)
  }
  
  return res
}

# 二叉树的中序遍历 (opens new window)

// 递归
function mid(root: TreeNode): void {
  if(!root) return
  mid(root.left)
  console.log(root.val)
  mid(root.right)
}

// 迭代
function mid(root: TreeNode): number[] {
  if(!root) return []
  const res: number[] = []
  const stack: TreeNode[] = [root]
  
  while(stack.length) {
    while(root) {
      stack.push(root)
      root = root.left
    }
    const node = stack.pop()
    res.push(node.val)
    root = node.right
  }
  
  return res.slice(0, res.length - 1)
}

# 二叉树的后序遍历 (opens new window)

// 递归
function post(root: TreeNode): void {
  if(!root) return
  post(root.left)
  post(root.right)
  console.log(root.val)
}

// 迭代
function post(root: TreeNode): number[] {
  if(!root) return []
  const res: number[] = []
  const stack: TreeNode[] = [root]
  while(stack.length) {
    const node = stack.pop()
    res.push(node.val)
    node.left && stack.push(node.left)
    node.right && stack.push(node.right)
  }
  
  return res.reverse()
}

# 二叉树的层序遍历 (opens new window)

function level(root: TreeNode): number[][] {
  const res: number[][] = []
  
  function recursion(node: TreeNode, level: number): void {
    if(!node) return 
    res[level] = res[level] || []
    res[level].push(node.val)
    
    recursion(node.left, level + 1)
    recursion(node.right, level + 1)
  }
  
  recursion(root, 0)
  
  return res
}

# 遍历变种

# 二叉树的锯齿形层次遍历 (opens new window)

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

function zigzagLevelOrder(root: TreeNode | null): number[][] {
  const res: number[][] = [];

  function traverse(node: TreeNode | null, level: number): void {
    if (!node) return;
    res[level] = res[level] || [];
    if (level % 2 === 0) {
      res[level].push(node.val);
    } else {
      res[level].unshift(node.val);
    }
    traverse(node.left, level + 1);
    traverse(node.right, level + 1);
  }

  traverse(root, 0);

  return res;
}

# 根据已知二叉树,求出具体值

# 求二叉树的最大深度 (opens new window)

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

function maxDepth(root: TreeNode | null): number {
  if (!root) return 0;

  return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}

# 二叉搜索树中第 K 小的元素 (opens new window)

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

function kthSmallest(root: TreeNode | null, k: number): number {
  let res: number;

  function mid(node: TreeNode | null): void {
    if (!node) return;
    mid(node.left);
    if (k === 0) return;
    else {
      res = node.val;
      k--;
    }
    mid(node.right);
  }

  mid(root);

  return res;
}

# 二叉树的最近公共祖先 (opens new window)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode, q: TreeNode): TreeNode {
  if(!root || root === p || root === q) return root
  
  const left = lowestCommonAncestor(root.left, p, q)
  const right = lowestCommonAncestor(root.right, p, q)
  
  if(left && right) {
    return root
  }
  
  return left || right
}

# 二叉树直径 (opens new window)

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

function diameterOfBinaryTree(root: TreeNode | null): number {
  let max = 0;

  function dfs(node: TreeNode | null): number {
    if (!node) return 0;
    const left = dfs(node.left);
    const right = dfs(node.right);

    max = Math.max(max, left + right);

    return Math.max(left, right) + 1;
  }

  dfs(root);
  return max;
}

# 求根到叶子结点数字之和 (opens new window)

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

function sumNumbers(root: TreeNode | null): number {
  let ans = 0;

  function dfs(node: TreeNode | null, s: string) {
    if (!node) return;
    s = s + node.val;
    if (!node.left && !node.right) {
      ans += +s;
      return;
    }
    dfs(node.left, s);
    dfs(node.right, s);
  }

  dfs(root, "");

  return ans;
}

# 一些特殊二叉树

# 判断二叉树是否是对称二叉树 (opens new window)

给定一个二叉树,检查它是否是镜像对称的。

function isSymmetric(root: TreeNode | null): boolean {
  function recursive(left: TreeNode | null, right: TreeNode | null): boolean {
    if (!left && !right) return true;
    if (!left || !right) return false;
    return (
      left.val === right.val &&
      recursive(left.left, right.right) &&
      recursive(left.right, right.left)
    );
  }

  return recursive(root, root);
}

# 验证二叉搜索树 (opens new window)

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。

function isValidBST(root: TreeNode | null): boolean {
  function recursive(
    node: TreeNode | null,
    left: number,
    right: number
  ): boolean {
    if (!node) return true;
    if (node.val <= left || node.val >= right) return false;
    return (
      recursive(node.left, left, node.val) &&
      recursive(node.right, node.val, right)
    );
  }
  return recursive(root, -Infinity, Infinity);
}

# 从前序和中序遍历序列构造二叉树 (opens new window)

根据一棵树的前序遍历与中序遍历构造二叉树。

# 思路

  1. 构造一个二叉树需要构建三个部分:root、左子树、右子树
  2. 左子树、右子树的构建又包括了:root、左子树、右子树
  3. 解题的关键点在于定位出根节点,划分出左子树、右子树,然后 递归 构建左右子树

# 具体做法

  1. preorder 数组的第一项肯定是根节点 -- 因为前序遍历的顺序是 根 -> 左 -> 右
  2. 由根节点,在 inorder 左->根->右 中划分出左、右子树的 inorder 序列
  3. 通过 inorder 中左右子树的节点个树,在 preorder 中确定左右子树的 preorder 序列
  4. 得到左右子树的 preorder 和 inorder 序列,就能递归构建左右子树。
function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
  const hashMap = new Map<number, number>()
  for(let i = 0; i < inorder.length; i++) {
    hashMap.set(inorder[i], i)
  }
  
  function recursive(pStart: number, pEnd: number, iStart: number, iEnd: number): TreeNode | null {
    if(pStart > pEnd) return null
    const rootVal = preorder[pStart] // 根节点的值
    const root = new TreeNode(rootVal) // 根节点
    const mid = hashMap.get(rootVal) // 根节点在 inorder 的索引
    const leftNum = mid - iStart // 左子树的节点树
    root.left = recursive(pStart + 1, pStart + leftNum, iStart, mid - 1)
    root.right = recursive(pStart + leftNum + 1, pEnd, mid + 1, iEnd)
    return root
  }
  
  return recursive(0, preorder.length - 1, 0, inorder.length - 1)
}

# 翻转二叉树 (opens new window)

翻转二叉树。

function invertTree(root: TreeNode | null): TreeNode | null {
  if(!root) return null
  const left = invertTree(root.left)
  const right = invertTree(root.right)
  
  root.left = right
  root.right = left
  
  return root
}

# 把二叉搜索树转换为累加树 (opens new window)

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

# 思路

利用反向中序遍历,右 -> 根 -> 左 的顺序依次累加

function convertBST(root: TreeNode | null): TreeNode | null {
  let cur = 0;
  function recursive(root: TreeNode | null): TreeNode | null {
    if (!root) return null;
    recursive(root.right);
    root.val += cur;
    cur = root.val;
    recursive(root.left);
    return root;
  }
  return recursive(root);
}

# 合并二叉树 (opens new window)

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

function mergeTrees(t1: TreeNode | null, t2: TreeNode | null): TreeNode | null {
  if(!t1) return t2
  if(!t2) return t1
  
  t1.val = t1.val + t2.val
  t1.left = mergeTrees(t1.left, t2.left)
  t1.right = mergeTrees(t1.right, t2.right)
  
  return t1
}

# 树的子结构 (opens new window)

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

# 思路

  1. 如果两棵树一模一样,则 B 是 A 的子树
  2. 通过递归遍历 A,让 A 的子树判断是否和 B 相同
function isSubStructure(A: TreeNode | null, B: TreeNode | null): boolean {
  if (!A || !B) return false;
  return (
    isSame(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)
  );
}

function isSame(A: TreeNode | null, B: TreeNode | null): boolean {
  if (!B) return true;
  if (!A) return false;
  if (A.val !== B.val) return false;
  return isSame(A.left, B.left) && isSame(A.right, B.right);
}

# 二叉树的镜像 (opens new window)

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

function mirrorTree(root: TreeNode | null): TreeNode | null {
  if (!root) return root;
  const left = mirrorTree(root.left);
  const right = mirrorTree(root.right);

  root.left = right;
  root.right = left;
  return root;
}

# 平衡二叉树 (opens new window)

给定一棵二叉树,判断它是否是高度平衡的二叉树。

# 思路

  1. 比较两颗子树的高度,两边都取最大值
  2. 查看两颗子树的高度差是否为 1
  3. 如果过大于 1,将其标记为 -1(表示不是平衡二叉树),然后每次递归时判断该节点的子树是否是平衡二叉树
function isBalanced(root: TreeNode | null): boolean {
  if (!root) return true;

  function height(node: TreeNode | null): number {
    if (!node) return -1;
    return Math.max(height(node.left), height(node.right)) + 1;
  }

  return (
    Math.abs(height(root.left) - height(root.right)) < 2 &&
    isBalanced(root.left) &&
    isBalanced(root.right)
  );
}

# 求二叉树的一些路径

# 路径总和III (opens new window)

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

# 思路

  1. 算出当前节点有多少符合条件的路线
  2. 再递归遍历左右子树,查看分别有多少符合条件的路径
  3. 加起来便是总数
function pathSum(root: TreeNode | null, sum: number): number {
  if (!root) return 0; // 若节点为空,则返回0
  const curSum = findSum(root, sum); // 查看当前节点有多少符合条件的路径
  const leftSum = pathSum(root.left, sum); // 遍历左子树看看有多少符合条件的路径
  const rightSum = pathSum(root.right, sum); // 遍历右子树看看有多少符合条件的路径
  return curSum + leftSum + rightSum;
}

// 辅助函数
function findSum(node: TreeNode | null, sum: number): number {
  if (!node) return 0; // 如果节点为空,则返回0
  const flag = node.val === sum ? 1 : 0; // 如果当前节点的值刚好相等,为1
  const leftSum = findSum(node.left, sum - node.val); // 算出左子树剩余可用的值
  const rightSum = findSum(node.right, sum - node.val); // 算出右子树剩余可用的值
  return flag + leftSum + rightSum;
}

# 路径总和II (opens new window)

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

function pathSum(root: TreeNode | null, sum: number): number[][] {
  const res: number[][] = [];

  function dfs(node: TreeNode | null, sum: number, path: number[]): void {
    if (!node) return;
    path.push(node.val);
    if (!node.left && !node.right) {
      if (node.val !== sum) return;
      else res.push([...path]);
    }
    if (node.left) {
      dfs(node.left, sum - node.val, path);
      path.pop();
    }
    if (node.right) {
      dfs(node.right, sum - node.val, path);
      path.pop();
    }
  }

  dfs(root, sum, []);

  return res;
}

# 二叉树的最大路径和 (opens new window)

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

function maxPathSum(root: TreeNode | null): number {
  let res = -Infinity;

  function dfs(node: TreeNode | null): number {
    if (!node) return 0;
    const left = Math.max(dfs(node.left), 0);
    const right = Math.max(dfs(node.right), 0);
    res = Math.max(res, left + right + node.val);
    return Math.max(left, right) + node.val;
  }
  dfs(root);

  return res;
}

# 其他

# 将二叉搜索树转换为一个排序的双向链表 (opens new window)

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表

对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。

class Node {
  constructor(val: number, left: Node | null, right: Node | null) {
    this.val = val
    this.left = left
    this.right = right
  }
}

function treeToDoublyList(node: Node | null): Node | null {
  if(!node) return node
  
  let head: Node | null = null
  let tail: Node | null = null
  let pre: Node | null = null
  
  function dfs(node: Node | null): void {
    if(!node) return node
    dfs(node.left)
    // 第一个节点作为头节点
    if(!pre) head = node
		// 将上一个节点的后继指针指向当前节点
    else pre.right = node
    // 将当前指针的前驱指针指向上一个节点
    node.left = pre
    // 更新上一个节点
    pre = node
    // 更新尾部节点
    tail = node
    dfs(node.right)
  }
  
  dfs(root)
  // 首尾连接
  head.left = tail
  tail.right = head
  return head
}

# 二叉树展开为链表 (opens new window)

给定一个二叉树,原地 将它展开为一个单链表。

function flatten(node: TreeNode | null): void {
  function dfs(root: TreeNode | null): TreeNode | null {
    if(!root) return;
    dfs(root.left);
    dfs(root.right);
    let pre = root.left;
    if(pre) {
    	//获取左子树最右叶子节点
      while(pre.right){
      	pre = pre.right;
      }
      //将右子树放在左子树最右右子节点后面
      pre.right = root.right;
      //将新构建的左子树放在右子树上
      root.right = root.left;
      //左子树置空
      root.left = null;
    }
  }
	dfs(root);
}