二叉树高频题目-上-不含树型dp

二叉树高频题目-上-不含树型dp

题目1 : 二叉树的层序遍历

  • 测试链接 : https://leetcode.cn/problems/binary-tree-level-order-traversal/

  • 思路

    • 自己使用数组实现队列, 在队列中进行广度优先遍历
    • 先将根结点进队, 如果队列里还有东西, 按照队列大小进行循环, 让队列里的结点进入本层的链表中, 如果正在处理的结点有子节点, 就让左右节点分别进入队列, 待每层的循环结束后, 将该层的列表装入总列表中
    • 待队列里没有节点, 返回
  • 代码O(n)

    • // 如果测试数据量变大了就修改这个值
      	public static int MAXN = 2001;// 没有数据类型的初始化,别忘了赋值
      
      	public static TreeNode[] queue = new TreeNode[MAXN];
      
      	public static int l, r;
      	// l: 拿数拿l位置的数,l++ r: 放数放r位置的数, r++
      
      	// 提交时把方法名改为levelOrder,此方法为每次处理一层的优化bfs,此题推荐
      	public static List<List<Integer>> levelOrder2(TreeNode root) {
      		List<List<Integer>> ans = new ArrayList<>();// 记录所有层的结点
      		if (root != null) {// 如果二叉树不为空
      			l = r = 0;// 逻辑清空
      			queue[r++] = root;// 先把根结点进队列
      			while (l < r) { // 队列里还有东西
      				int size = r - l;// 队列大小
      				ArrayList<Integer> list = new ArrayList<Integer>();// 用于记录该层结点
      				for (int i = 0; i < size; i++) {// 如下行为重复size遍
      					TreeNode cur = queue[l++];// 弹出一个节点
      					list.add(cur.val);// 把它放在本层的链表里去
      					if (cur.left != null) {// 有左加左
      						queue[r++] = cur.left;
      					}
      					if (cur.right != null) {// 有右加右
      						queue[r++] = cur.right;
      					}
      				}
      				ans.add(list);// ans接收这一层的链表
      			}
      		}
      		return ans;
      	}
      

题目2 : 二叉树的锯齿形层序遍历

  • 测试链接 : https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/

  • 思路

    • 先判断根节点是否为空, 为空直接返回ans
    • 不为空, 加入根结点
    • 先收集结点
      • reverse == false, 左 -> 右, l…r-1, 收集size个
      • reverse == true, 右 -> 左, r-1…l, 收集size个
      • 左 -> 右, i = i + 1
      • 右 -> 左, i = i - 1
    • 后进行广度优先遍历
    • 搜集该层 并 置reverse = !reverse
    • 返回ans
  • 代码O(n)

    • public class Code02_ZigzagLevelOrderTraversal {
      
      	// 不提交这个类
      	public static class TreeNode {
      		public int val;
      		public TreeNode left;
      		public TreeNode right;
      	}
      
      	// 提交以下的方法
      	// 用每次处理一层的优化bfs就非常容易实现
      	// 如果测试数据量变大了就修改这个值
      	public static int MAXN = 2001;
      
      	public static TreeNode[] queue = new TreeNode[MAXN];
      
      	public static int l, r;
      
      	public static List<List<Integer>> zigzagLevelOrder(TreeNode root) {
      		List<List<Integer>> ans = new ArrayList<>();
      		if (root != null) {
      			l = r = 0;
      			queue[r++] = root;
      			// false 代表从左往右
      			// true 代表从右往左
      			boolean reverse = false; 
      			while (l < r) {
      				int size = r - l;
      				ArrayList<Integer> list = new ArrayList<Integer>();
      				
      				// 1. 收集结点
      				// reverse == false, 左 -> 右, l....r-1, 收集size个
      				// reverse == true,  右 -> 左, r-1....l, 收集size个
      				// 左 -> 右, i = i + 1
      				// 右 -> 左, i = i - 1
      				for (int i = reverse ? r - 1 : l, j = reverse ? -1 : 1, k = 0; k < size; i += j, k++) {
      					TreeNode cur = queue[i];
      					list.add(cur.val);// 加的是.val
      				}
      				
      				// 2. 广度优先遍历
      				for (int i = 0; i < size; i++) {
      					TreeNode cur = queue[l++];
      					if (cur.left != null) {
      						queue[r++] = cur.left;
      					}
      					if (cur.right != null) {
      						queue[r++] = cur.right;
      					}
      				}
      				ans.add(list);// 收集该层
      				reverse = !reverse;// 反转
      			}
      		}
      		return ans;
      	}
      
      }
      

题目3 : 二叉树的最大特殊宽度

  • 测试链接 : https://leetcode.cn/problems/maximum-width-of-binary-tree/

  • 思路

    • 将每一个节点进行编号(此处从1开始), nq为节点队列, iq为id队列
    • 每次BFS结点入队时编号也要入队
    • 当每层节点入队完成, 最大宽度: 最右结点编号 - 最左结点编号 + 1
    • 选取最大宽度
  • 代码O(n)

    • // 用每次处理一层的优化bfs就非常容易实现
      	// 如果测试数据量变大了就修改这个值
      	public static int MAXN = 3001;
      
      	public static TreeNode[] nq = new TreeNode[MAXN];// 结点队列
      
      	public static int[] iq = new int[MAXN];// id队列
      
      	public static int l, r;
      
      	public static int widthOfBinaryTree(TreeNode root) {
      		l = r = 0;// 初始化结点队列
      		
      		int ans = 1;// 根
      		// 同步放根
      		nq[r] = root;// nq里面放root
      		iq[r++] = 1;// iq里面放1
      		
      		while (l < r) {// 结点队列有值
      			int size = r - l;
      			// 左	...	右
      			// l	...	r-1
      			ans = Math.max(ans, iq[r - 1] - iq[l] + 1);// 取最大的宽度
      			for (int i = 0; i < size; i++) {// 一次处理一层的BFS
      			   // 弹出
      				TreeNode node = nq[l];
      				int id = iq[l++];
      				// 有左放左
      				if (node.left != null) {
      					nq[r] = node.left;
      					iq[r++] = id * 2;// 注意自己的编号
      				}
      				// 有右放右
      				if (node.right != null) {
      					nq[r] = node.right;
      					iq[r++] = id * 2 + 1;// 注意自己的编号
      				}
      			}
      		}//while
      		
      		
      		return ans;
      	}
      

题目4 : 求二叉树的最大深度、求二叉树的最小深度

1. 最大深度
  • 测试链接 : https://leetcode.cn/problems/maximum-depth-of-binary-tree/

  • 思路

    • 递归
    • 当前结点是否为空?
      • 为空, 返回深度: 0
      • 不为空, 返回左右子树最大深度 + 1(本层所占的深度)
  • 代码O(n)

    public static int maxDepth(TreeNode root) {
    		return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    	}
    
2. 最小深度

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

如下图: a -> b -> c 为有效路径 a -> b -> null 为无效路径

(有叶子结点一定要走到叶子结点)

请添加图片描述

  • 测试链接 : https://leetcode.cn/problems/minimum-depth-of-binary-tree/

  • 思路

    • 递归

    • 当前的树是空树. 最小深度为0

    • 当前树只有根结点, 最小深度为1

    • 当前树不是以上两种情况

      • 设置两个变量标识最右最小深度, 初始化为整数最大值
      • 若左子树不为空, 递归计算左子树的最小深度
      • 若右子树不为空, 递归计算右子树的最小深度
      • 返回左右子树的最小深度+1(当前层所占深度)
    • 代码O(n)

      • 	public int minDepth(TreeNode root) {// 从根数到某个叶子结点
        		if (root == null) {
        			// 当前的树是空树
        			return 0;
        		}
        		if (root.left == null && root.right == null) {
        			// 当前root是叶节点
        			return 1;
        		}
        		int ldeep = Integer.MAX_VALUE;
        		int rdeep = Integer.MAX_VALUE;
        		// 只能调有节点的一边, 否则会被干扰, 计算出一个不符合要求的最小深度
        		if (root.left != null) {
        			ldeep = minDepth(root.left);
        		}
        		if (root.right != null) {
        			rdeep = minDepth(root.right);
        		}
        		return Math.min(ldeep, rdeep) + 1;
        	}
        

题目5 : 二叉树先序序列化和反序列化

注意:中序遍历无法完成二叉树的序列化和反序列化,代码中给出了说明。后序遍历可以但不再详述。

  • 测试链接 : https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/

  • 思路

    • 序列化

      • 使用按照先序遍历的路线, 有值用StringBuilder追加 " 值 + ‘,’ “, 为空则追加”#,", 递归操作
    • 反序列化

      • 将拼接后的字符串用’,'分割为字符串数组, 使用全局变量 cnt来指示要处理的数组下标, 使用变量cur来读取当前正在处理的字符串
      • 若cur为#, 返回null
      • 若cur不为#, 则建立新的头结点, 赋值为cur的int值, 递归赋值左右节点, 返回头节点
    • 代码O(n)

      •     // 二叉树可以通过先序、后序或者按层遍历的方式序列化和反序列化
            // 但是,二叉树无法通过中序遍历的方式实现序列化和反序列化
            // 因为不同的两棵树,可能得到同样的中序序列,即便补了空位置也可能一样。
            // 比如如下两棵树
            //         __2
            //        /
            //       1
            //       和
            //       1__
            //          \
            //           2
            // 补足空位置的中序遍历结果都是{ null, 1, null, 2, null}
        	// 提交这个类
        	public class Codec {
        
        		public String serialize(TreeNode root) {
        			StringBuilder builder = new StringBuilder();// 使用StringBuilder
        			f(root, builder);// 递归调用序列化
        			return builder.toString();
        		}
        
        		void f(TreeNode root, StringBuilder builder) {// 使用的是原来的builder, 返回void即可
        			// 类似于二叉树的先序遍历
        			if (root == null) {// 为null, 直接停
        				builder.append("#,");// 占位
        			} else {
        				builder.append(root.val + ",");// 加值
        				f(root.left, builder);// 左树序列化
        				f(root.right, builder);// 右树序列化
        			}
        		}
        		
        		
        		// 当前数组消费到哪了
        		public static int cnt;// (全局变量方便)
        
        		public TreeNode deserialize(String data) {
        			String[] vals = data.split(",");// 用,分割出每一个值
        			cnt = 0;// 要处理的字符下标
        			return g(vals);// 递归消费
        		}
        
        		TreeNode g(String[] vals) {
        			// !!!每个条件判断下都有一个出口
        			String cur = vals[cnt++];// 用1个变量保存, 否则容易多++
        			if (cur.equals("#")) {
        				return null;// 空结点
        			} else {
        				TreeNode head = new TreeNode(Integer.valueOf(cur));// 建立有效结点
        				head.left = g(vals);// 左树消费
        				head.right = g(vals);// 右树消费
        				return head;
        			}
        		}
        
        	}
        

题目6 : 二叉树按层序列化和反序列化

  • 测试链接 : https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/

  • 思路

    • 序列化

      • 先判空
      • 层次遍历中, 每次进队列之前进行序列化
    • 反序列化

      • 先判空
      • 将字符串分割, 依次消耗数组中的值
        • 先建立根结点, 根结点入队
        • 当队列中还有结点
          • 取出当前结点, 消耗当前后两位的值, 生成结点的左右孩子, 左右孩子中谁不为空,谁进队列,先加左,再加右
      • 返回根结点
    • 代码O(n)

      • 	public class Codec {
        
        		// 用于按层遍历
        		public static int MAXN = 10001;
        
        		public static TreeNode[] queue = new TreeNode[MAXN];
        
        		public static int l, r;
        
        		public String serialize(TreeNode root) {
        			StringBuilder builder = new StringBuilder();
        			if (root != null) {// 树不为空,追加字符串
        			// 进队列之前,序列化
        				builder.append(root.val + ",");
        				// 初始化队列
        				l = 0;
        				r = 0;
        				queue[r++] = root;
        				while (l < r) {
        					root = queue[l++];// 弹出当前结点
        					if (root.left != null) {// 左孩子不为空
        						// 进队列之前,序列化
        						builder.append(root.left.val + ",");// 左孩子的值,不要写错!!!
        						queue[r++] = root.left;
        					} else {// 没有左: 序列化 但 不加到队列
        						builder.append("#,");
        					}
        					
        					if (root.right != null) {
        						// 进队列之前,序列化
        						builder.append(root.right.val + ",");// 右孩子的值,不要写错!!!
        						queue[r++] = root.right;
        					} else {// 没有右: 序列化 但 不加到队列
        						builder.append("#,");
        					}
        				}// while
        			}
        			
        			// endIf: 如果root==null,返回""(空串)
        			return builder.toString();// 返回序列化后的字符串
        		}
        
        		public TreeNode deserialize(String data) {
        			if (data.equals("")) {// root == null
        				return null;
        			}
        			String[] nodes = data.split(",");// 分割字符串
        			int index = 0;// 当前消耗的下标
        			TreeNode root = generate(nodes[index++]);// 建立根结点
        			// 初始化队列
        			l = 0;
        			r = 0;
        			queue[r++] = root;// 分割出的根结点 加入 队列
        			while (l < r) {// 队列不为空
        				TreeNode cur = queue[l++];// 弹出一个结点
        				// 直接把根结点的左右孩子消费,建立出来
        				cur.left = generate(nodes[index++]);
        				cur.right = generate(nodes[index++]);
        				
        				// 左右孩子中谁不为空,谁进队列,先加左,再加右
        				if (cur.left != null) {
        					queue[r++] = cur.left;
        				}
        				if (cur.right != null) {
        					queue[r++] = cur.right;
        				}
        			}// end while
        			
        			
        			return root;// 返回根结点
        		}
        
        		private TreeNode generate(String val) {
        			return val.equals("#") ? null : new TreeNode(Integer.valueOf(val));
        		}
        
        	}
        

题目7 : 利用先序与中序遍历序列构造二叉树

  • 测试链接 : https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

  • 思路

    • 特殊判断, 输入不合法的情况
    • 根据先序遍历找出每一次递归的根结点, 使用根节点将中序遍历分为两半, 左半边为对应的左子树, 先序遍历中 根节点以后数对应数目的结点 也为左子树的节点; 右半边为对应的右子树, 先序遍历中 根结点以后数对应数目的结点 也为右子树的节点.
    • 递归, 分别用以下方式递归构造左右子树: 给出先序遍历 及 处理子树下标范围, 给出中序遍历 及 处理子树下标范围, 根据第二点建立
  • 代码O(n)

    • 	public static TreeNode buildTree(int[] pre, int[] in) {
      		// 特殊判断, 不合法情况
      		if (pre == null || in == null || pre.length != in.length) {
      			return null;
      		}
      		// 设置一张表, 用于加速查找对应节点在中序遍历的位置
      		HashMap<Integer, Integer> map = new HashMap<>();
      		for (int i = 0; i < in.length; i++) {
      			map.put(in[i], i);
      		}
      		// 开始建树
      		return f(pre, 0, pre.length - 1, in, 0, in.length - 1, map);// 输入的下标, 看好, 不要越界
      	}
      
      	public static TreeNode f(int[] pre, int l1, int r1, int[] in, int l2, int r2, HashMap<Integer, Integer> map) {
      		// 特殊判断, 不合法情况
      		if (l1 > r1) {
      			return null;
      		}
      		// 建头结点
      		TreeNode head = new TreeNode(pre[l1]);
      		// 只有一个数, 直接返回头结点即可
      		if (l1 == r1) {
      			return head;
      		}
      		
      		// 查出中序遍历的位置
      		int k = map.get(pre[l1]);
      		// pre : l1(........)[.......r1]
      		// in  : (l2......)k[........r2]
      		// (...)是左树对应,[...]是右树的对应
      		head.left = f(pre, l1 + 1, l1 + k - l2, in, l2, k - 1, map);
      		head.right = f(pre, l1 + k - l2 + 1, r1, in, k + 1, r2, map);
      		return head;
      	}
      

题目8 : 验证完全二叉树

  • 测试链接 : https://leetcode.cn/problems/check-completeness-of-a-binary-tree/

  • 思路

    • 使用BFS, 挨个遍历, 判断每一个节点是否违规
      • 第一个违规条件: 当前节点有右子树无左子树
      • 第二个违规条件: 遇到过孩子不双全的节点当前结点不是叶节点
    • 若出现违规节点, 则该二叉树不是完全二叉树
    • 若所有节点都符合要求, 则该二叉树完全二叉树 (空树是完全二叉树)
  • 代码O(n)

    • 	// 如果测试数据量变大了就修改这个值
      	
      	// 用于BFS
      	public static int MAXN = 101;
      
      	public static TreeNode[] queue = new TreeNode[MAXN];
      
      	public static int l, r;
      
      	public static boolean isCompleteTree(TreeNode h) {
      		if (h == null) {// 判空
      			return true;
      		}
      		
      		// BFS
      		l = r = 0;
      		queue[r++] = h;
      		
      		// 是否遇到过左右两个孩子不双全的节点
      		boolean leaf = false;
      		// 开始遍历
      		while (l < r) {
      			// 1.弹出一个结点
      			h = queue[l++];
      			
      			// 2.判断是不是违规
      				// 第一个违规条件: 有右无左			第二个违规条件: 遇到过孩子不双全的节点 且 该结点不是叶节点
      			if ((h.left == null && h.right != null) || (leaf && (h.left != null || h.right != null))) {
      				return false;
      			}
      			
      			// 3.BFS
      			if (h.left != null) {
      				queue[r++] = h.left;
      			}
      			if (h.right != null) {
      				queue[r++] = h.right;
      			}
      			
      			// 4.判断该节点是不是孩子不双全节点
      			if (h.left == null || h.right == null) {
      				// h.left == null: 左右孩子都没有; h.right == null: 没有右孩子
      				leaf = true;
      			}
      		}// end while
      		return true;// 没有违规, 返回true
      	}
      

题目9 : 求完全二叉树的节点个数,要求时间复杂度低于O(n)

  • 测试链接 : https://leetcode.cn/problems/count-complete-tree-nodes/

  • 思路

    • 递归
    • 根据根节点右子树 的 最左节点 能否到达最深层, 从而判断出为满二叉树的一侧和需要进行递归计算的一侧
      • 到达最深层, 说明根节点的左子树为满二叉树, 节点个数为 2^(子树深度) - 1, 左子树节点个数加上根节点个数后的总节点个数为2^(子树深度); 右子树一定为完全二叉树, 递归调用即可
      • 不能到达最深层, 说明根节点的右子树为满二叉树, 且右子树的深度比根结点所在树的深度少1, 节点个数为2^(子树深度) - 1, 右子树节点个数加上根节点个数后的总节点个数为2^(子树深度); 左子树一定为完全二叉树, 递归调用即可
  • 代码O(h^2) h == log2(n)

    • 	public static int countNodes(TreeNode head) {
      		// 判空
      		if (head == null) {
      			return 0;
      		}
      		return f(head, 1, mostLeft(head, 1));
      	}
      
      	// cur : 当前来到的节点
      	// level :  当前来到的节点在第几层
      	// h : 整棵树的高度,不是cur这棵子树的高度
      	// 求 : cur这棵子树上有多少节点
      	public static int f(TreeNode cur, int level, int h) {
      		// BaseCase: 当前结点就是最深层的结点 即 叶子结点
      		if (level == h) {
      			return 1;// 只有这一个结点
      		}
      		
      		// 判断cur右树上的最左节点 是否 到达最深层, 从而判断出为 满二叉树的一侧 和 需要递归调用的一侧
      		if (mostLeft(cur.right, level + 1) == h) {
      			// cur右树上的最左节点,扎到了最深层
      			return (1 << (h - level)) + f(cur.right, level + 1, h);
      			// 	cur左树 + 我自己 的深度  cur右树递归计算
      		} else {
      			// cur右树上的最左节点,没扎到最深层
      			return (1 << (h - level - 1)) + f(cur.left, level + 1, h);
      			// cur右树 + 我自己 的深度      cur左树递归计算
      		}
      	}
      
      	// 当前节点是cur,并且它在level层
      	// 返回从cur开始不停往左,能扎到几层
      	public static int mostLeft(TreeNode cur, int level) {
      		while (cur != null) {// 不能用cur.left判断, 因为cur可能为空
      			level++;
      			cur = cur.left;
      		}
      		return level - 1;
      	}
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值