面试题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;
}
}