【LeetCode笔记】树的专项练习


推荐视频:

一周刷爆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;
    }
}

中序遍历(单栈)

中序遍历 – 左头右

整棵树左边界进栈,依次弹出的过程中打印,对弹出节点的右树重复。

打印顺序是 左头右

  1. 将左节点循环入栈 s.push(root); root = root.left;
  2. 若左节点不存在 则说明 上一次压栈的节点为 头节点
  3. 将头节点 出栈后 指针指向其右节点 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);// 判断右树是否为 二叉搜索树
    }
}

是否完全二叉树

特点:除了叶子节点外,其余节点都有左节点和右节点。

img

要点:

  1. 任意节点 有右无左 false
  2. 满足第一条,遇见的第一个左右孩子不全,那接下来遇到的所有节点都必须为 叶节点(不存在 左节点 和右节点)

例图:

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;
        }
    }
}

节点的公共祖先

获取某节点的全部祖先节点:使用递归方法

  1. 先获取 节点Q 的父节点集 顺序为(Q,Q的祖先,Q的祖祖先,Q的祖祖祖先)
  2. 获取 节点P 的父节点集 (P,P的祖先,P的祖祖先,P的祖祖祖先)
  3. 利用双层 for 循环 在 两个节点集中
  4. 找到第一个 相等的节点 返回答案
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;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值