当前位置: 首页 > article >正文

【算法】图解面试笔试热点二叉树相关算法题汇总

目录

1.前中后序遍历

1.1.递归序实现

1.2.非递归实现

2.二叉树的宽度优先遍历

2.1.求二叉树的最大宽度

3.判断一棵树是否是搜索二叉树

4.判断一棵树是否是完全二叉树

5.判断一棵树是否是满二叉树

6.判断一棵树是否是平衡二叉树

7.最低公共祖先

8.找到后继节点

9.二叉树的序列化与反序列化

10.纸条折痕问题


二叉树的节点结构

public static class Node {
  public int value;
  public Node left;
  public Node right;
  
  public Node(int data) {
    this.value = data;
  }
}

1.前中后序遍历

1.1.递归序实现

递归的方法遍历二叉树时每一个节点都会被访问三次

public static void f(Node head) {
  //第1次访问当前节点
  if (head == null) {
    return;
  }
  //第1次访问结束
  f(head.left);
  //第2次访问当前节点
  //第2次访问结束
  f(head.right);
  //第3次访问当前节点
  //第3次访问结束
}

递归序
1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1

在递归序的基础上衍生出了三种顺序的遍历,先序中序后序

先序:先打印头节点,再打印左子树上所有的节点,最后打印右子树上所有的节点。可以利用递归序,仅当第一次来到一个节点时才打印

public static void preOrderRecur(Node head) {
  if (head == null) {
    return;
  }
  System.out.print(head.value + " ");
  preOrderRecur(head.left);
  preOrderRecur(head.right);
}
先序(根左右)
1,2,4,5,3,6,7

中序:先打印左子树上所有的节点,再打印头节点,最后打印右子树上所有的节点。可以利用递归序,仅当第二次来到一个节点时才打印

public static void inOrderRecur(Node head) {
  if (head == null) {
    return;
  }
  inOrderRecur(head.left);
  System.out.print(head.value + " ");
  inOrderRecur(head.right);
}
//中序(左根右)
4,2,5,1,6,3,7

后序:先打印左子树上所有的节点,再打印右子树上所有的节点,最后打印头节点。可以利用递归序,仅当第三次来到一个节点时才打印

public static void posOrderRecur(Node head) {
  if (head == null) {
    return;
  }
  posOrderRecur(head.left);
  posOrderRecur(head.right);
  System.out.print(head.value + " ");
}
//后序(左右根)
4,5,2,6,7,3,1

1.2.非递归实现

准备一个栈

  • 先序遍历

首先根节点入栈

(1) 每次从栈中弹出一个节点cur

(2) 处理(打印)cur

(3) 如果可以,先把右孩子压入栈,再把左孩子压入栈

(4) 重复这个过程

public static void preOrderRecur(Node head) {
  if (head != null) {
    Stack<Node> stack = new Stack<Node>();
    stack.add(head);
    while (!stack.isEmpty()) {
      head = stack.pop();
      System.out.print(head.value + " ");
      if (head.right != null) {
        stack.push(head.right);
      }
      if (head.left != null) {
        stack.push(head.left);
      }
    }
  }
  System.out.println();
}
  • 后序遍历

我们在先序遍历中做以下改动

额外准备一个收集栈(总共有两个栈)

首先根节点入栈

(1) 每次从栈中弹出一个节点cur

(2) 把cur放入收集栈中

(3) 如果可以,先把左孩子压入栈中,再把右孩子压入栈中

(4) 重复这个过程

(5) 弹出收集栈中的所有元素并打印

public static void posOrderUnRecur1(Node head) {
  if (head != null) {
    Stack<Node> s1 = new Stack<Node>();
    Stack<Node> s2 = new Stack<Node>();
    s1.push(head);
    while (!s1.isEmpty()) {
      head = s1.pop();
      s2.push(head);
      if (head.left != null) {
        s1.push(head.left);
      }
      if (head.right != null) {
        s1.push(head.right);
      }
    }
    while (!s2.isEmpty()) {
      System.out.print(s2.pop().value + " ");
    }
  }
  System.out.println();
}
  • 中序遍历

先把整棵树的左边界全部压入栈,左边界指从当前根节点开始一直.left直至null

在依次弹出并打印节点的过程中对当前节点的右子树重复这个操作

1
2
3
4
5
6
7
8
9
public static void inOrderUnRecur(Node head) {
  if (head != null) {
    Stack<Node> stack = new Stack<Node>();
    while (!stack.isEmpty() || head != null) {
      if (head != null) {
        stack.push(head);
        head = head.left;
      } else {
        head = stack.pop();
        System.out.print(head.value + " ");
        head = head.right;
      }
    }
  }
  System.out.println();
}

2.二叉树的宽度优先遍历

先序遍历就是二叉树的深度优先遍历

宽度优先遍历

初始化一个队列

(1) 先把头节点存入队列

(2) 每一次拿出节点并打印,同时先把左子树存入队列,再把右子树存入队列

public static void w(Node head) {
  if (head == null) {
    return;
  }
  Queue<Node> queue = new LinkedList<>();
  queue.add(head);
  while (!queue.isEmpty()) {
    Node cur = queue.poll();
    System.out.print(cur.value + " ");
    if (cur.left != null) {
      queue.add(cur.left);
    }
    if (cur.right != null) {
      queue.add(cur.right);
    }
  }
}

2.1.求二叉树的最大宽度

(1) 使用哈希表

需要一个机制来确定遍历的过程中,当前层的宽度是多少,即确定当前节点属于第几层。准备一张表,记录当前节点属于第几层

public static int w(Node head) {
  if (head == null) {
    return 0;
  }
  Queue<Node> queue = new LinkedList<>();
  queue.add(head);
  HashMap<Node, Integer> levelMap = new HashMap<>();
  levelMap.put(head, 1);
  int curLevel = 1;  //当前处于第几层
  int curlevelNodes = 0;  //当前层有几个节点
  int max = Integer.MIN_VALUE;
  while (!queue.isEmpty()) {
    Node cur = queue.poll();
    int curNodeLevel = levelMap.get(cur);
    if (curNodeLevel == curLevel) {
      curLevelNodes++;
    } else {
      max = Math.max(max, curLevelNodes);
      curLevel++;
      curLevelNodes = 1;
    }
    if (cur.left != null) {
      levelMap.put(cur.left, curNodeLevel + 1);
      queue.add(cur.left);
    }
    if (cur.right != null) {
      levelMap.put(cur.right, curNodeLevel + 1);
      queue.add(cur.right);
    }
  }
  return max;
}

(2) 使用队列

第一个变量:当前层最后一个节点 Node curEnd

当前遍历的节点所在的层中,这一层的最后一个节点是谁

第二个变量:下一层最后一个节点 Node nextEnd

当前遍历的节点所在的层中,下一层的最后一个节点是谁

第三个变量:当前层已经发现的节点数 int curLevel

第四个变量:全局变量 int max

3.判断一棵树是否是搜索二叉树

搜索二叉树(二叉排序树)的特点是左子树全部小于根节点,右子树全部大于根节点

如果中序遍历的结果是升序的,那么这棵二叉树就是搜索二叉树

(1) 递归

  • 方法一

//全局变量保存上一个节点的值
public static int preValue = Integer.MIN_VALUE;
​
public static boolean isBST(Node head) {
  if (head == null) {
    return true;
  }
  boolean isLeftBst = isBST(head.left);
  if (!isLeftBst) {
    return false;
  }
  if (head.value <= preValue) {
    return false;
  } else {
    preValue = head.value;
  }
  return isBST(head.right);
}
  • 方法二

  • 树型DP

一棵搜索二叉树:

(1) 左子树是搜索二叉树

(2) 右子树是搜索二叉树

(3) 左子树的最大值小于当前节点

(4) 右子树的最小值大于当前节点

左子树与右子树需要提供的信息是一样的:自己是否是搜索二叉树?自己的最大值是多少?自己的最小值是多少?

//信息体
public static class ReturnData {
  public boolean isBST;
  public int min;
  public int max;
  
  public ReturnData(boolean is, int mi, int ma) {
    isBST = is;
    min = mi;
    max = ma;
  }
}
public static ReturnData process(Node x) {
  if (x == null) {
    return null;
  }
  ReturnData leftData = process(x.left);
  ReturnData rightData = process(x.right);
  int min = x.value;
  int max = x.value;
  if (leftData != null) {
    min = Math.min(min, leftData.min);
    max = Math.max(max, leftData.max);
  }
  if (rightData != null) {
    min = Math.min(min, rightData.min);
    max = Math.max(max, rightData.max);
  }
  boolean isBST= true;
  if (leftData != null && (!leftData.isBST || leftData.max >= x.value)) {
    isBST = false;
  }
  if (rightData != null && (!rightData.isBST || x.value >= rightData.min)) {
    isBST = false;
  }
  return new ReturnData(isBST, min, max);
}

(2) 非递归

public static boolean isBST2(Node head) {
  if (head != null) {
    int preValue = Integer.MIN_VALUE;
    Stack<Node> stack = new Stack<>();
    while (!stack.isEmpty() || head != null) {
      if (head != null) {
        stack.push(head);
        head = head.left;
      } else {
        head = stack.pop();
        if (head.value <= preValue) {
          return false;
        } else {
          preValue = head.value;
        }
        head = head.right;
      }
    }
  }
  return true;
}

4.判断一棵树是否是完全二叉树

完全二叉树要么是满二叉树,要么是从左往右依次变满的二叉树

按照宽度遍历二叉树

(1) 任何一个节点,有右孩子却无左孩子则返回false

(2) 如果遇到了第一个左右孩子不全的情况,接下来的所有节点都必须是叶子结点

public static boolean isCBT(Node head) {
  if (head == null) {
    return true;
  }
  LinkedList<Node> queue = new LinkedList<>();
  boolean leaf = false;
  Node l = null;
  Node r = null;
  queue.add(head);
  while (!queue.isEmpty()) {
    head = queue.poll();
    l = head.left;
    r = head.right;
    if ((leaf && (l != null || r != null)) || (l == null && r != null)) {
      return false;
    }
    if (l != null) {
      queue.add(l);
    }
    if (r != null) {
      queue.add(r);
    }
    if (l == null || r == null) {
      leaf = true;
    }
  }
  return true;
}

5.判断一棵树是否是满二叉树

设最大深度L,节点数N

满二叉树满足 N = (2^L) - 1

  • 树型DP

//信息体
public static class Info {
  public int height;
  public int nodes;
  
  public Info(int h, int n) {
    height = h;
    nodes = n;
  }
}
public static Info f(Node) {
  if (x == null) {
    return new Info(0, 0);
  }
  Info leftData = f(x.left);
  Info rightData = f(x.right);
  int height = Math.max(leftData.height, rightData.height) + 1;
  int nodes = leftData.nodes + rightData.nodes + 1;
  return new Info(height, nodes);
}
public static boolean isF(Node head) {
  if (head == null) {
    return true;
  }
  Info data = f(head);
  return data.nodes == (1 << data.height - 1);
}

6.判断一棵树是否是平衡二叉树

对于平衡二叉树来说,任何一棵子树左右子树的高度差都不超过1

  • 树型DP

一棵平衡二叉树:

(1) 左子树是平衡二叉树

(2) 右子树是平衡二叉树

(3) 左右子树的高度差不超过1

左子树与右子树需要提供的信息是一样的:自己是否是平衡的?自己的高度是多少?

//信息体
public static class ReturnType {
  public boolean isBalanced;
  public int height;
  
  public ReturnType(boolean isB, int hei) {
    isBalanced = isB;
    height = hei;
  }
}
public static ReturnType process(Node x) {
  if (x == null) {
    return new ReturnType(true, 0);
  }
  ReturnType leftData = process(x.left);
  ReturnType rightData = process(x.right);
  int height = Math.max(leftData.height, rightData.height) + 1;
  boolean isBalanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) < 2;
  return new ReturnType(isBalanced, height);
}

7.最低公共祖先

问:

给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点,例:下图中D与G的最低公共祖先为B,E与G的最低公共祖先为E

答:

找到两个节点所在的链最初汇聚的节点

public static Node lowestAncestor(Node head, Node o1, Node o2) {
  if (head == null || head == o1 || head == o2) {
    return head;
  }
  Node left = lowestAncestor(head.left, o1, o2);
  Node right = lowestAcestor(head.right, o1, o2);
  if (left != null && right != null) {
    return head;
  }
  return left != null ? left : right;
}

8.找到后继节点

问:

在二叉树中找到一个节点的后继节点

现在有一种新的二叉树节点类型如下

public class Node {
  public int value;
  public Node left;
  public Node right;
  public Node parent;
  public Node (int val) {
    value = val;
  }
}

该结构比普通二叉树节点结构多了一个指向父节点的parent指针

假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null

只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数

在中序遍历中,节点的下一个节点叫做后继节点,节点的前一个节点叫做前驱节点

答:

D的后继节点是B、B的后继节点是E、E的后继节点是A...

(1) 当x有右子树时,x的后继是右子树上的最左节点

(2) 当x无右子树时,从x开始,检查自己是不是父节点的左孩子,如果不是则继续向上检查,是则返回父节点

(3) 整棵树最右面的节点是没有后继的,它的后继为null

public static Node getSuccessorNode(Node node) {
  if (node == null) {
    return node;
  }
  if (node.right != null) {
    return getLeftMost(node.right);
  } else {
    Node parent = node.parent;
    //循环判断自己是不是父节点的左子节点
    while (parent != null && parent.left != node) {
      node = parent;
      parent = node.parent;
    }
    return parent;
  }
}
public static Node getLeftMost(Node node) {
  if (node == null) {
    return node;
  }
  while (node.left != null) {
    noed = node.left;
  }
  return node;
}

9.二叉树的序列化与反序列化

问:

内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

答:

先序遍历方式序列化,下划线表示一个值的结束

public static String serialByPre(Node head) {
  if (head == null) {
    return "#_";
  }
  String res = head.value + "_";
  res += serialByPre(head.left);
  res += serialByPre(head.righht);
  return res;
}
public static Node reconByPreString(String preStr) {
  String[] values = preStr.split("_");
  Queue<String> queue = new LinkedList<String>();
  for (int i = 0; i != values.length; i++) {
    queue.add(values[i]);
  }
  return reconPreOrder(queue);
}
public static Node recponPreOrder(Queue<String> queue) {
  String value = queue.poll();
  if (value.equals("#")) {
    return null;
  }
  Node head = new Node(Integer.valueOf(value));
  head.left = reconPreOrder(queue);
  head.right = reconPreOrder(queue);
  return head;
}

10.纸条折痕问题

问:

将一根纸条竖起来,每次向自己的方向对折,假设对折N次,输出凹折痕与凸折痕的分布情况

答:

不管当前折痕为凹还是为凸,该折痕的左子节点必定为凹折痕,右子节点必定为凸折痕

将二叉树中序遍历即可

//递归过程,来到了某一节点
//i是节点的层数,N是总共有几层,down == true则凹false则凸
public static void printProcess(int i, int N, boolean down) {
  if (i > N) {
    return;
  }
  printProcess(i + 1, N, true);
  System.out.println(down ? "凹" : "凸");
  printProcess(i + 1, N, false);
}
public static void printAllFolds(int N) {
  printProcess(1, N, true);
}

http://www.kler.cn/a/520446.html

相关文章:

  • 01机器学习入门
  • 单相可控整流电路——单相桥式全控整流电路
  • 在Windows系统中本地部署属于自己的大语言模型(Ollama + open-webui + deepseek-r1)
  • 【Project】CupFox电影网站数据爬取分析与可视化
  • 枚举与模拟 练习
  • LabVIEW橡胶动态特性测试系统
  • 人工智能:从基础到前沿
  • el-autocomplete组件模糊查询及显示空白解决方法
  • 【蓝桥杯】43695.填字母游戏
  • 【Linux】gcc/g++的使用
  • 淘宝商品数据解析的具体步骤是什么?
  • go单元测试和基准测试
  • wow-agent---task4 MetaGPT初体验
  • CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据)
  • MATLAB编写遗传算法【Genetic Algorithm(GA)】求解函数最大值
  • [NOIP2007]矩阵取数游戏
  • 开发技巧,vue 中的动态组件的引用 component + is
  • 性能测试网络风险诊断有哪些?
  • 跟我学C++中级篇——容器的连接
  • vue3入门基础学习之搭建登录验证功能
  • MyBatis Plus 中常用的 Service 功能
  • RocketMq创建消费者组
  • 数字化创新者如何利用开源2+1链动模式AI智能名片S2B2C商城小程序源码重塑市场地位
  • AUTOSAR从入门到精通-汽车SOA架构
  • Ubuntu 20.04 x64下 编译安装ffmpeg
  • 链表oj练习