面试题32、32-Ⅱ、32-Ⅲ、33

面试题32.从上到下打印二叉树

在这里插入图片描述
二叉树的从上至下打印(即按层打印),又称为二叉树的 广度优先搜索

class Solution {
    public int[] levelOrder(TreeNode root) {
		if(root == null) return new int[0];
		Queue<TreeNode> queue = new LinkedList<>();
		List<Integer> list = new ArrayList<>();
		queue.add(root);
		while(!queue.isEmpty()) {
			TreeNode node = queue.poll();
			list.add(node.val);
			if(node.left != null) queue.add(node.left);
			if(node.right != null) queue.add(node.right);
		}
		int[] res = new int[list.size()];
		for(int i = 0; i < list.size(); i++) {
			res[i] = list.get(i);
		}
		return res;
    }
}
  • 时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次。
  • 空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

————————————————————————————————————————

面试题32-Ⅱ.从上到下打印二叉树Ⅱ

在这里插入图片描述

BFS

BFS 循环:

  • 1.新建一个临时列表 tmp,用于存储当前层打印结果

  • 2.当前层打印循环:循环次数为当前层的节点数(即队列 queue 长度);

    • 1.出队:队首元素出队,记为 node;
    • 2.打印:将 node.val 添加至 tmp 尾部
    • 3.添加子节点:若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue;
  • 3.将当前层结果 tmp 加入 res

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
		Queue<TreeNode> queue = new LinkedList<>();
		List<List<Integer>> res = new ArrayList<>();
		if(root == null) return res;
		queue.add(root);
		while(!queue.isEmpty()) {
			List<Integer> tmp = new ArrayList<>();
			//BFS打印,len表示的是当层的结点数
			int len = queue.size();
			for(int i = 0; i < len; i++) {
				TreeNode node = queue.poll();
				tmp.add(node.val);
				//左右子节点如果不为空就加入到队列中
				if(node.left != null) queue.add(node.left);
				if(node.right != null) queue.add(node.right);
			}
			//把每层的节点值存储在res中
			res.add(tmp);
		}
		return res;
    }
}
  • 时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次。
  • 空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

DFS

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
		List<List<Integer>> res = new ArrayList<>();
		levelHelper(res, root, 0);
		return res;
    }
    //level表示当前递归到第几层了,从第0层开始
    public void levelHelper(List<List<Integer>> res, TreeNode root, int level) {
    	//边界条件判断
    	if(root == null) return;
    	//level表示的是层数,如果level >= list.size(),说明到下一层了,所以
    	//要先把下一层的list初始化,防止下面add的时候出现空指针异常
    	if(level >= res.size()) res.add(new ArrayList<>());
    	//level表示的是第几层,这里访问到第几层,我们就把数据加入到第几层
    	res.get(level).add(root.val);
    	//当前节点访问完之后,再使用递归的方式分别访问当前节点的左右子节点
    	levelHelper(res, root.left, level + 1);
    	levelHelper(res, root.right, level + 1);
    }
}

————————————————————————————————————————

面试题32-Ⅲ.从上打下打印二叉树Ⅲ

在这里插入图片描述

BFS

传统的 BFS 打印数据都是从左开始打印,如下图:
在这里插入图片描述
而这道题要求先从左边,下一层从右边,再下一层又从左边…左右交替。针对这种情况,可以使用一个标志位 leftToRight,如果是 true 就表示从左向右打印,否则就从右向左打印。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
		List<List<Integer>> res = new ArrayList<>();
		if(root == null) return res;
		//创建队列,保存节点
		Queue<TreeNode> queue = new LinkedList<>();
		queue.add(root);
		boolean leftToRight = true; //第一层先从左边开始打印
		while(!queue.isEmpty()) {
			//临时保存每一层的节点值
			LinkedList<Integer> tmp = new LinkedList<>();
			//记录每一层有多少个节点
			int len = queue.size();
			//循环遍历取出这一层的所有节点
			for(int i = 0; i < len; i++) {
				TreeNode node = queue.poll();
				//如果为真,则说明这一层节点要从左往右打印
				if(leftToRight) {
					//依次把节点加入到末尾即可
					tmp.addLast(node.val);
				} else {
					//要加入到链表的头部,以达到从右往左的打印顺序
					tmp.addFirst(node.val);
				}
				//左右子节点如果不为空会被加入到队列中
				if(node.left != null) queue.add(node.left);
				if(node.right != null) queue.add(node.right);
			}
			//把这一层的节点值加入到res中
			res.add(tmp);
			//改变下次访问的方向
			leftToRight = !leftToRight;
		}
		return res;
    }
}
  • 时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次,占用 O(N) ;双端队列的队首和队尾的添加和删除操作的时间复杂度均为 O(1) 。
  • 空间复杂度 O(N) : 最差情况下,即当树为满二叉树时,最多有 N/2 个树节点 同时 在 deque 中,使用 O(N) 大小的额外空间。

DFS

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
		List<List<Integer>> res = new ArrayList<>();
		if(root == null) return res;
		travel(root, res, 0);
		return res;
    }
    //level表示当前走到第几层了,从0开始
    public void travel(TreeNode root, List<List<Integer>> res, int level) {
    	if(root == null) return;
    	//level表示的是层数,如果level >= list.size(),说明到下一层了,所以
    	//要先把下一层的list初始化,防止下面add的时候出现空指针异常
    	if(level >= res.size()) {
    		List<Integer> list = new LinkedList<>();
    		res.add(list);
    	}
    	//根节点是第0层,所以偶数层是从左到右遍历的,只需要依次从集合末尾添加
    	if(level % 2 == 0) {
    		//遍历到第几层就操作第几层数据
    		res.get(level).add(root.val);
    	//如果是奇数层,要从右往左遍历,所以要依次从集合头部添加
    	} else {
    		res.get(level).add(0, root.val);
    	}
    	travel(root.left, res, level + 1);
    	travel(root.right, res, level + 1);
    }
}

————————————————————————————————————————

面试题33.二叉搜索树的后序遍历序列

在这里插入图片描述

递归

二叉搜索树的特点是左子树的值<根节点<右子树的值。而其后序遍历的顺序是:左子节点→右子节点→根节点

在数组中,第一个大于根节点的节点,它之前的属于左子树区间,而它及它以后的属于右子树区间,必须满足左子树区间内的值均小于根节点的值,右子树区间内的值均大于根节点的值,若不满足,则说明不是二叉搜索树,返回false。在此基础上,不断递归每个区间,判断每个区间的左右子树是否依旧满足。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
		return dfs(postorder, 0, postorder.length - 1);
    }
    public boolean dfs(int[] postorder, int left, int right) {
    	//如果 left == right,就一个节点不需要判断了。
    	//left > right 说明已经没有节点了,也不用继续判断了。
    	if(left >= right) return true;
    	//因为数组中最后一个值postorder[right]是根节点,这里从左往右找出第一个比
    	//根节点大的值,他后面的都是根节点的右子节点(包含当前值,不包含最后一个值,
    	//因为最后一个是根节点),他前面的都是根节点的左子节点
    	int mid = left;
    	int value = postorder[right]; //根节点值
    	while(postorder[mid] < value) {
    		mid++;
    	}
    	//因为postorder[mid]前面的值都是比根节点root小的,
    	//我们还需要确定postorder[mid]后面的值都要比根节点root大,
   	 	//如果后面有比根节点小的直接返回false
    	int temp = mid;
    	while(temp < right) {
    		if(postorder[temp] < value) return false;
    		temp++;
    	}
    	//然后对左右子节点进行递归调用
    	return dfs(postorder, left, mid - 1) && dfs(postorder, mid, right - 1);
    }
}

单调栈

在这里插入图片描述

它的后续遍历结果是:[3,6,5,9,8,11,13,12,10]。

从前往后看好像没什么规律,那从后往前看它的倒序数组:[10,12,13,11,8,9,5,6,3]

规律

1、挨着的两个数如果arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点。10和12是挨着的并且10<12,所以12是10的右子节点。比arr[i]大的肯定都是他的右子节点,如果还是挨着他的,肯定是在后续遍历中所有的右子节点最后一个遍历的,所以他一定是arr[i]的右子节点。

2、如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个值是大于arr[i+1]中最小的。 比如13,11是降序的,那么11肯定是他前面某一个节点的左子节点,并且这个值是大于11中最小的,我们看到12和13都是大于11的,但12最小,所以11就是12的左子节点。

解决思路

  • 1、遍历数组的所有元素,如果栈为空,就把当前元素压栈

  • 2、如果栈不为空,并且当前元素大于栈顶元素,说明是升序的,那么就说明当前元素是栈顶元素的右子节点,就把当前元素压栈,如果一直升序,就一直压栈。

  • 3、当前元素小于栈顶元素,说明是倒序的,说明当前元素是某个节点的左子节点,我们目的是要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点。

补充:

  • 往右子树遍历的过程,value是越来越大的,一旦出现了value小于栈顶元素value的时候,就表示要开始进入左子树了。但是这个左子树是从哪个节点开始的呢?
  • 单调栈帮我们记录了这些节点,只要栈顶元素还比当前节点大,就表示还是右子树,要移除,因为我们要找到这个左孩子节点直接连接的父节点,也就是找到这个子树的根,只要栈顶元素还大于当前节点,就要一直弹出,直到栈顶元素小于节点,或者栈为空。栈顶的上一个元素就是子树节点的根。
  • 接下来,数组继续往前遍历,之后的左子树的每个节点,都要比子树的根要小,才能满足二叉搜索树,否则就不是二叉搜索树。
class Solution {
    public boolean verifyPostorder(int[] postorder) {
		Stack<Integer> stack = new Stack<>();
		int parent = Integer.MAX_VALUE;
		for(int i = postorder.length - 1; i >= 0; i--) {
			int cur = postorder[i];
			//如果当前节点小于栈顶元素,说明栈顶元素和当前值构成了倒叙,
        	//说明当前节点是前面某个节点的左子节点,我们要找到他的父节点
			while(!stack.isEmpty() && stack.peek() > cur) {
				parent = stack.pop();
			}
			//只要遇到了某一个左子节点,才会执行上面的代码,才会更
        	//新parent的值,否则parent就是一个非常大的值,也就
        	//是说如果一直没有遇到左子节点,那么右子节点可以非常大
			if(cur > parent) return false;
			stack.add(cur);
		}
		return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值