226. 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
递归思路
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
关键在于遍历顺序,遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果,前中后序应该选哪一种遍历顺序?
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
递归三要素:
- 确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
public TreeNode invertTree(TreeNode root)
- 确定终止条件
当前节点为空的时候,就返回
if (root == null) {
return null;
}
- 确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
invertTree(root.left);
invertTree(root.right);
swap(root);
难点
- 为什么用中序很麻烦?
原二叉树如图,中序遍历的顺序是:左中右
- 先遍历到左边最下面的节点1,发现1的左孩子为空,就返回到2,swap(2)
- 回退到4,swap(4)
- 处理完4后开始遍历右子树,但右子树2已经处理过了,反而是7没处理过,所以就出现问题
前序遍历
// 交换节点的左右孩子
public void swap (TreeNode root) {
TreeNode tempNode = root.left;
root.left = root.right;
root.right = tempNode;
}
// 前序遍历
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
swap(root); // 中
invertTree(root.left); // 左
invertTree(root.right); // 右
return root;
}
后序遍历
// 交换节点的左右孩子
public void swap (TreeNode root) {
TreeNode tempNode = root.left;
root.left = root.right;
root.right = tempNode;
}
// 后序遍历
public TreeNode invertTree1(TreeNode root) {
if (root == null) {
return null;
}
invertTree(root.left); // 左
invertTree(root.right); // 右
swap(root); // 中
return root;
}
迭代思路
迭代前序遍历二叉树,将左右子节点入栈,每次pop时将其子节点swap,再讲swap后的字节点入栈**(不用管先左后右还是先右后左)**
// 迭代-----------------------
public TreeNode invertTree2(TreeNode root) {
// 定义栈存放节点
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
// 前序:中左右
TreeNode node = stack.pop();
swap(node);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return root;
}
// 迭代-----------------------
广度优先遍历思路
同理层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍
public TreeNode invertTree3(TreeNode root) {
Deque<TreeNode> que = new ArrayDeque<>();
if (root != null) {
que.push(root);
}
while (!que.isEmpty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode node = que.poll();
swap(node);
if (node.right != null) {
que.push(node.right);
}
if (node.left != null) {
que.push(node.left);
}
}
}
return root;
}
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
递归思路
对称比较的是:两个子树的内侧节点是否相等和外侧节点是否相等(左子树的右节点是否等于右子树的左节点和左子树的左节点是否等于右子树的右节点)
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
递归三要素
- 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数就是左子树节点和右子树节点。
返回值自然是bool类型。
- 确定终止条件
要比较两个节点数值相不相同,分不同情况讨论
左节点 | 右节点 | 返回值 |
---|---|---|
空 | 不空 | false |
不空 | 空 | false |
空 | 空 | true |
不空但值不等 | 不空但值不等 | false |
不空值相等 | 不空值相等 | 向下遍历 |
if (left == null && right != null) return false;
if (left != null && right == null) return false;
if (left == null && right == null) return true;
if (left.val != right.val) return false;
- 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
// 递归法
public boolean isSymmetric(TreeNode root) {
return compare(root.left, root.right);
}
boolean compare(TreeNode left, TreeNode right) {
if (left == null && right != null) return false;
if (left != null && right == null) return false;
if (left == null && right == null) return true;
if (left.val != right.val) return false;
// 现在只剩下左右都不是空且值相等的情况
// 往下比较外侧
boolean compareOutside = compare(left.left, right.right);
// 往下比较内侧
boolean compareInside = compare(left.right, right.left);
// 要内外侧都相等才对称
return compareOutside && compareInside;
}
// 递归法
迭代思路
在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。
这里用队列:
- 先将根节点的左右子节点入队,然后pop2个判断
- 如果有值且相等就继续将左节点的左右子树入队,继续弹出判断
难点
本人写代码,无论是用到栈结构,队列结构,都习惯用deque,因为deque支持在头部和尾部插入或删除元素。
但deque有 ArrayDeque 和 LinkedList 两种框架。
主要区别在于LinkedList支持插入null元素
Deque<Node> que = new ArrayDeque<>()
就通过不了,只能用Deque<Node> que = new ArrayDeque<>()
// 迭代法:队列
public boolean isSymmetric1(TreeNode root) {
Deque<TreeNode> que = new LinkedList<>();
if (root != null) {
que.offer(root.left);
que.offer(root.right);
}
while (!que.isEmpty()) {
TreeNode leftNode = que.poll();
TreeNode rightNode = que.poll();
// 四种情况
if (leftNode == null && rightNode == null) {
continue;
}
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
que.offer(leftNode.left);
que.offer(rightNode.right);
que.offer(leftNode.right);
que.offer(rightNode.left);
}
return true;
}
// 迭代法