文章目录
推荐视频:
一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解_哔哩哔哩_bilibili
树的遍历
递归序:看视频吧
- 先序遍历 – 头左右
- 先序遍历的变体 – 头右左 (跟后续遍历 相反 即 使用双栈可以实现后序遍历 )
- 中序遍历 – 左头右
- 后续遍历 – 左右头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nXaCHly-1646635857542)(C:\Users\dancerHoan\AppData\Roaming\Typora\typora-user-images\image-20220301100725128.png)]
class Solution {
public void preOrder(TreeNode root){
if(root==null){
return;
}
//先序遍历 在这里处理节点 System.out.println(root.val)
preOrder(root.left,l);
//中序遍历 在这里处理节点 System.out.println(root.val)
preOrder(root.right,l);
//后序遍历 在这里处理节点 System.out.println(root.val)
}
}
先序遍历(单栈)
实现步骤:
Stack<Node> s = new Stack<>();
1.从栈中弹出一个节点 Node n = s.pop();
2.打印(处理) 该节点
3.将孩子节点 从右至左 依次压栈
List<Node> children = n.children;
for(int i=children.size()-1;i>=0;i--){
Node node = children.get(i);
s.push(node);
}
4.循环 while(!s.isEmpty())
例题:
589. N 叉树的前序遍历 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public List<Integer> preorder(Node root) {
Stack<Node> s = new Stack<>();
List<Integer> l = new ArrayList<>();
if (root == null) {
return l;
}
s.push(root);
while (!s.isEmpty()) {
Node n = s.pop();
l.add(n.val);
List<Node> children = n.children;
for(int i=children.size()-1;i>=0;i--){
Node node = children.get(i);
s.push(node);
}
// 倒叙遍历,当然这个方法很蠢 可以先将链表反转如下
// Collections.reverse(node.children);
// for (Node item : node.children) {
// stack.add(item);
// }
}
return l;
}
}
144. 二叉树的前序遍历 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> l = new ArrayList<>();
Stack<TreeNode> s = new Stack<>();
if(root==null){
return l;
}
s.push(root);
while(!s.isEmpty()){
TreeNode n = s.pop();
l.add(n.val);
if(n.right !=null){
s.push(n.right);
}
if(n.left != null){
s.push(n.left);
}
}
return l;
}
}
中序遍历(单栈)
中序遍历 – 左头右
整棵树左边界进栈,依次弹出的过程中打印,对弹出节点的右树重复。
打印顺序是 左头右
- 将左节点循环入栈
s.push(root); root = root.left;
- 若左节点不存在 则说明 上一次压栈的节点为 头节点
- 将头节点 出栈后 指针指向其右节点
root = root.right;
Tips:
- 每次出栈的 肯定是头节点
- 先入左👉若左无👉则出栈👉指针指向出栈节点 即头节点👉指向其右节点
例题:
94. 二叉树的中序遍历 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> l = new ArrayList<>();
Stack<TreeNode> s = new Stack<>();
if(root == null){
return l;
}
while(!s.isEmpty()||root!=null){
if(root!=null){
s.push(root);
root = root.left;
}else{
root = s.pop();
l.add(root.val);
root = root.right;
}
}
return l;
}
}
后续遍历(双栈)
- 先序遍历 – 头左右
- 先序遍历的变体 – 头右左 (跟后续遍历 相反 即 使用双栈可以实现后序遍历 )
- 后续遍历 – 左右头
**先序遍历:**①将头节点压栈 ②while循环 ③弹栈,打印(处理)节点 ④将该节点的孩子节点 由右至左压栈 ⑤周而复始
先序遍历的变体: 仅变 Step④将该节点的孩子节点 由左至右压栈
例题:
145. 二叉树的后序遍历 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> l = new ArrayList<>();
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
if(root==null) {
return l;
}
s1.push(root);
while (!s1.isEmpty()) {
TreeNode pop = s1.pop();
if(pop.left!=null){
s1.push(pop.left);
}
if(pop.right!=null){
s1.push(pop.right);
}
s2.push(pop);
}
while (!s2.isEmpty()){
l.add(s2.pop().val);
}
return l;
}
}
590. N 叉树的后序遍历 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public List<Integer> postorder(Node root) {
List<Integer> l = new ArrayList<>();
Stack<Node> s1 = new Stack<>();
Stack<Node> s2 = new Stack<>();
if(root==null){
return l;
}
s1.push(root);
while(!s1.isEmpty()){
Node n = s1.pop();
s2.push(n);
List<Node> c = n.children;
for(int i=0;i<c.size();i++){
s1.push(c.get(i));
}
}
while (!s2.isEmpty()){
l.add(s2.pop().val);
}
return l;
}
}
层次遍历(队列)
用队列实现
Queue<TreeNode> q = new LinkedList<>();
q.offer(root); // 先将头节点 压入队列
while(q.isEmpty()){
TreeNode t = q.poll();// 弹出队列头节点
System.out.println(t.val);// 对节点进行处理
if(t.left!=null) q.offer(t.left);// 将孩子节点 依次入队
if(t.right!=null) q.offer(t.right);// 将孩子节点 依次入队
}
如何记载 每一层的节点数(使用哈希表)
HashMap<TreeNode,Integer> levelmap = new HashMap<>();
while(!deque.isEmpty()){
Node popNode = deque.poll();
if (map.get(popNode) == thisFloor) {// 若是该层元素
// thisFloor是当前层
++thisFloorNum;
} else {// 若已经进入下一层中的节点
max = Math.max(max, thisFloorNum);
++thisFloor;
thisFloorNum = 1;
}
// 每次添加都要在map中记录节点层数
if (popNode.leftNode != null) {
deque.add(popNode.leftNode);
map.put(popNode.leftNode, thisFloor + 1);
// 通过map给 节点的所在层数赋值
}
if (popNode.rightNode != null) {
deque.add(popNode.rightNode);
map.put(popNode.rightNode, thisFloor + 1);
}
}
例题:
102. 二叉树的层序遍历 - 力扣(LeetCode) (leetcode-cn.com)
利用队列实现BFS(广度优先算法)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<>();
if (root == null) {
return ret;
}
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size();// 可以记载当前层的 节点数量
List<Integer> l = new ArrayList<>();
while (size > 0) {
TreeNode poll = q.poll();// 将当前层 的节点弹出 并且将其孩子节点依次加入
l.add(poll.val);
if (poll.left != null) {
q.offer(poll.left);
}
if (poll.right != null) {
q.offer(poll.right);
}
size--;
}
ret.add(l);
}
return ret;
}
}
二叉树的最大深度
解题方法 用哈希表记录每个节点的 所在层数。
Map<TreeNode, Integer> map = new HashMap<>();
map.put(root, 1);
例题:
104. 二叉树的最大深度 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public int maxDepth(TreeNode root) {
int max = 0;
if (root == null) {
return max;
}
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
Map<TreeNode, Integer> map = new HashMap<>();
map.put(root, 1);
while (!q.isEmpty()) {
TreeNode poll = q.poll();
Integer level = map.get(poll);
max = Math.max(level, max);
if (poll.left != null) {
map.put(poll.left, level + 1);
q.offer(poll.left);
}
if (poll.right != null) {
map.put(poll.right, level + 1);
q.offer(poll.right);
}
}
return max;
}
}
剑指 Offer 55 - I. 二叉树的深度 - 力扣(LeetCode) (leetcode-cn.com)
判断树的类型
是否搜索二叉树
特性:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
要点: 中序遍历的结果为升序
例题:
98. 验证二叉搜索树 - 力扣(LeetCode) (leetcode-cn.com)
解法三:正经递归
class ReturnData {
boolean isBst;
int min;
int max;
public ReturnData(boolean isBst, int min, int max) {
this.isBst = isBst;
this.min = min;
this.max = max;
}
}
class Solution {
public boolean isValidBST(TreeNode root) {
return isBST(root).isBst;
}
public ReturnData isBST(TreeNode n) {
// 首先判断 是否为平衡二叉树 需要的条件为
// 左树 右树 是否为平衡二叉树
// 根节点 和 左树的最大值 和 右树的最小值关系
// 我需要 三个值 1.树是否为平衡二叉树 2.左子树的最大值 3.右子树的最小值
if (n == null) {
return null;
}
ReturnData left = isBST(n.left);
ReturnData right = isBST(n.right);
int min = n.val;// 当前树的最小值
int max = n.val;// 当前树的最大值
if (left != null) {
min = Math.min(min, left.min);// 当前树的最小值 和其左树 右树都有关
max = Math.max(max, left.max);
}
if (right != null) {
min = Math.min(min, right.min);
max = Math.max(max, right.max);
}
boolean isBst = true;
if (left != null) {
if (left.max >= n.val || !left.isBst) {
isBst = false;
}
}
if (right != null) {
if (right.min <= n.val || !right.isBst) {
isBst = false;
}
}
return new ReturnData(isBst, min, max);
}
}
解法一:迭代方法
class Solution {
public boolean isValidBST(TreeNode root) {
// int val = Integer.MIN_VALUE;
long val = Long.MIN_VALUE;
Stack<TreeNode> s = new Stack<>();
while(!s.isEmpty()||root!=null){
if(root==null){
TreeNode pop = s.pop();
if(pop.val<=val){
return false;
}else{
val = pop.val;
}
root = pop.right;
}else {
s.push(root);
root = root.left;
}
}
return true;
}
}
解法二:递归序
class Solution {
long a = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null){
return true;
}
boolean validBST = isValidBST(root.left);// 判断左树是否为 二叉搜索树
if(!validBST){
return false;
}
if(root.val>a){
a = root.val;
}else{
return false;
}
return isValidBST(root.right);// 判断右树是否为 二叉搜索树
}
}
是否完全二叉树
特点:除了叶子节点外,其余节点都有左节点和右节点。
要点:
- 任意节点 有右无左 false
- 满足第一条,遇见的第一个左右孩子不全,那接下来遇到的所有节点都必须为 叶节点(不存在 左节点 和右节点)。
例图:
无
class Solution {
public boolean isValidBST(TreeNode root) {
Queue<TreeNode> q = new LinkedList<>();
if (root == null) {
return false;
}
q.offer(root);
boolean leaf = false;
// 设定是否出现第一个叶子节点
// 因为第一个叶子节点之后的所有节点 都是叶子节点 才符合完全二叉树标准 即需要记录是否到达叶子节点
while (!q.isEmpty()) {
TreeNode node = q.poll();
TreeNode l = node.left;
TreeNode r = node.right;
if (l == null && r != null) {
// 左孩子为空 右孩子不为空 纯纯的非完全二叉树
return false;
}
if (leaf == true && (l != null || r != null)) {
// 当前处于叶子节点了 但是 他还拥有左孩子或右孩子的话 他就不满足叶子节点要求 直接判false
return false;
}
if(l!=null){
q.offer(l);
}
if(r!=null){
q.offer(r);
}
if(l==null||r==null){
// 若当前节点 不是满孩子即左右孩子只存在一个 则说明他之后的节点都是叶子节点 即将leaf置为true
leaf = true;
}
}
return true;
}
}
是否满二叉树
解法一:满二叉树的最大深度 和 节点个数满足 n = 2^level - 1
;
解法二:通过广度优先遍历(层次遍历),并且记录当前层level++
和当前层的节点个数queue.size()
是否平衡二叉树
视频:一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解_哔哩哔哩_bilibili
时间戳:0:46:25 不想看了,优点难。
对于任何一个节点来说:左树高度 与 右树高度 高度差≤1。
例题:110. 平衡二叉树 - 力扣(LeetCode) (leetcode-cn.com)
解法一:逻辑没错 就是超时了
class Solution {
public boolean isBalanced(TreeNode root) {
return isBad(root).isb;
}
public ReturnData isBad(TreeNode n){
if(n==null){
return new ReturnData(0,true);
}
int leftHight = isBad(n.left).height;
int rightHight = isBad(n.right).height;
boolean leftisb = isBad(n.left).isb;
boolean rightisb = isBad(n.right).isb;
boolean isb = true;
if(Math.abs(rightHight-leftHight)>1 || !(leftisb&&rightisb)){
// 如果高度差大于1 或者 左树 和右树其中一个 不是平衡二叉树
isb = false;
}
int height = Math.max(leftHight,rightHight)+1;// 当前树的节点树
return new ReturnData(height,isb);
}
}
class ReturnData {
int height;
boolean isb;
public ReturnData(int height) {
this.height = height;
}
public ReturnData(int height, boolean isb) {
this.height = height;
this.isb = isb;
}
}
方法二:看了题解,用高度来决定是否为平衡二叉树。
class Solution {
public boolean isBalanced(TreeNode root) {
return isBad(root)>=0;
}
public int isBad(TreeNode n){
if(n==null){
return 0;
}
int leftHight = isBad(n.left);
int rightHight = isBad(n.right);
if(Math.abs(rightHight-leftHight)>1 || leftHight==-1 || rightHight==-1){
// 如果高度差大于1 或者 左树 和右树其中一个 不是平衡二叉树
return -1;
}else{
return Math.max(rightHight,leftHight)+1;
}
}
}
节点的公共祖先
获取某节点的全部祖先节点:使用递归方法
- 先获取 节点Q 的父节点集 顺序为(Q,Q的祖先,Q的祖祖先,Q的祖祖祖先)
- 获取 节点P 的父节点集 (P,P的祖先,P的祖祖先,P的祖祖祖先)
- 利用双层 for 循环 在 两个节点集中
- 找到第一个 相等的节点 返回答案
public void getFatherMap(TreeNode root, Map<TreeNode, TreeNode> fatherMap) {
// 此方法 并没有 将根节点的 父节点传入 所以要在外面写
// fatherMap(root,null);
if (root == null) {
return;
}
fatherMap.put(root.left, root);
fatherMap.put(root.right, root);
getFatherMap(root.left, fatherMap);
getFatherMap(root.right, fatherMap);
}
左成云讲得:
public class Main {
public static Node ancestor(Node header, Node o1, Node o2) {
if (header == null || o1 == header || o2 == header)
return header;
Node lNode = ancestor(header.leftNode, o1, o2);
Node rNode = ancestor(header.rightNode, o1, o2);
// 该条件只会成功一次,返回的header就是我们所要找的节点
// 当该条件成立时,header结果就找到了,我们接下来的目的就是向上传递直至结束该递归调用
// 由于我们不知道这个header节点是它的父节点的左还是右
// 但是我们知道成功进入该条件后的所有递归中只能出现一边为null,另一边为header节点
// 所以 返回: lNode != null ? lNode : rNode
// 另外这句话也会在找到目标节点前将o1或o2传到上一个递归中,代表着这个路径上存在o1或o2
// 当路径上没有o1或o2时,lNode和rNode均为空,随便返回一个
if (lNode != null && rNode != null)
return header;
return lNode != null ? lNode : rNode;
}
static class Node {
int value;
Node leftNode;
Node rightNode;
}
}
例题:
235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)
暴力方法:时间 7%,空间 5%
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Map<TreeNode, TreeNode> map = new HashMap<>();// key为节点 value为该节点的母节点
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> l = new ArrayList<>();
map.put(root, null);// 设置头节点的母节点为 null
if (p.val == q.val) {
return p;
}
if (root == null) {
return root;
}
queue.offer(root);
// 通过层次遍历 得到节点-母节点的 Map集合
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
if (poll.left != null) {
queue.offer(poll.left);
map.put(poll.left, poll);
}
if (poll.right != null) {
queue.offer(poll.right);
map.put(poll.right, poll);
}
}
List<TreeNode> pMotherNodes = getMotherNodes(map, p);// 获得p的母节点
List<TreeNode> qMotherNodes = getMotherNodes(map, q);// 获得q的母节点
for(int i=0;i<pMotherNodes.size();i++){
for(int j=0;j<qMotherNodes.size();j++){
if(pMotherNodes.get(i) == qMotherNodes.get(j)){
return pMotherNodes.get(i);
}
}
}
return root;
}
public List<TreeNode> getMotherNodes(Map<TreeNode, TreeNode> map, TreeNode t) {
List<TreeNode> l = new ArrayList<>();
l.add(t);
while (t != null) {
TreeNode treeNode = map.get(t);
if (treeNode != null) {
l.add(treeNode);
}
t = treeNode;
}
return l;
}
}
一次遍历:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
int pp = p.val;
int qq = q.val;
while(true){
if((pp<= root.val && qq>= root.val)||(pp>= root.val && qq<= root.val)){
return root;
}else if(pp > root.val && qq> root.val){
root = root.right;
}else if(pp < root.val && qq< root.val){
root = root.left;
}
}
}
}