剑指 Offer 26. 树的子结构 ⭐️
递归 ⭐️ ⭐️ ⭐️
参考:K佬题解
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
// 终止条件(题目规定空树不是任意一个树的子结构)
if (A == null || B == null) {
return false;
}
// 若当前已经满足,直接返回
if (check(A, B)) return true;
// 递归遍历A的左子树 和 A的右子树中是否包含 子结构B
return isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
// 判断以A为根节点的树中是否包含 子结构B
boolean check(TreeNode A, TreeNode B) {
// 1)节点B为空,说明树B已经遍历完了,则返回true
if (B == null) return true;
// 2) 节点A为空,说明树A的当前树枝已经遍历完了,则返回false
if (A == null) return false;
// 对应位置节点值不相等,则返回false
if (A.val != B.val) return false;
// 递归检查二者的左、右子树是否符合条件
return check(A.left, B.left) && check(A.right, B.right);
}
}
层次遍历 BFS
一个队列 TLE
一开始用“一个队列”进行 BFS,然后卡在 45/48
组样例,超时了
超时代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
// 超时了
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
// 题目规定空树不是任意一个树的子结构
if (A == null || B == null) {
return false;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(A);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
// A中当前节点值和B节点值相等时,则判断A中当前节点的所在子树是否包含子结构B
if (node.val == B.val) {
// 找到满足条件的,立刻返回!
if (check(node, B)) {
return true;
}
}
// 保证队列中不存在null节点
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
return false;
}
// 检查以A为根节点的树中是否包含 子结构B
boolean check(TreeNode A, TreeNode B) { // TLE
Queue<TreeNode> queue = new LinkedList<>();
// 同时入队两个节点
queue.offer(A);
queue.offer(B);
while (!queue.isEmpty()) {
for (TreeNode root : queue) {
System.out.print((root == null ? -1 : root.val) + ", ");
}
System.out.println();
// 同时出队两个节点
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
// A和B中一个为空,一个不为空,则不满足
if (node1 == null && node2 != null) return false;
// 对应位置节点值不相等,不满足条件
if (node1.val != node2.val) return false;
// 继续比较二者当前节点的左、右子树是否满足条件
if (node2.left != null) { // 剪枝处理(保证B对应的节点一定非null)
queue.offer(node1.left);
queue.offer(node2.left);
}
if (node2.right != null) { // 剪枝处理(保证B对应的节点一定非null)
queue.offer(node1.right);
queue.offer(node2.right);
}
}
return true;
}
}
两个队列 AC
在 check
中换用两个队列,则可以 AC。(暂时还没找到原因 \捂脸\)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
// 题目规定空树不是任意一个树的子结构
if (A == null || B == null) {
return false;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(A);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
// A中当前节点值和B节点值相等时,则判断A中当前节点的所在子树是否包含子结构B
if (node.val == B.val) {
// 找到满足条件的,立刻返回!
if (check(node, B)) {
return true;
}
}
// 保证队列中不存在null节点
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
return false;
}
// 检查以A为根节点的树中是否包含 子结构B
boolean check(TreeNode A, TreeNode B) {
Queue<TreeNode> queueA = new LinkedList<>();
Queue<TreeNode> queueB = new LinkedList<>();
// 同时入队两个节点
queueA.offer(A);
queueB.offer(B);
while (!queueB.isEmpty()) {
// 同时出队
TreeNode node1= queueA.poll();
TreeNode node2 = queueB.poll();
// A为空,B不为空(其实已经保证了queueB中不会存在null节点),则不满足
if (node1 == null && node2 != null) return false;
// 对应位置节点值不相等,不满足条件
if (node1.val != node2.val) return false;
// 继续比较二者当前节点的左、右子树是否满足条件
if (node2.left != null) { // 剪枝处理(保证B对应的节点一定非null)
queueA.offer(node1.left);
queueB.offer(node2.left);
}
if (node2.right != null) { // 剪枝处理(保证B对应的节点一定非null)
queueA.offer(node1.right);
queueB.offer(node2.right);
}
}
return true;
}
}
剑指 Offer 27. 二叉树的镜像
递归
写法一:不带返回值
第一种递归的写法比较直观,但是需要单独定义一个 不带返回值的函数 void dfs(root)
思路:
- 每次遍历时,交换当前节点的左、右孩子节点
遍历二叉树的方式可以采用 前序、后序、层次遍历,但是不能使用中序遍历。这里采用的是“前序”
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
dfs(root);
return root;
}
void dfs(TreeNode root) {
if (root == null) return;
// 前序
// 交换每个节点的左右孩子
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
dfs(root.left);
dfs(root.right);
}
}
写法二:带返回值 ⭐️
参考:K佬题解 动画详解
思路 🤔
- 本题其实是需要遍历整颗树,且需要对“树的结构进行修改”,其实也可以直接使用题目给的带有返回值
TreeNode
的递归函数 - 使用“后序”遍历,并用
rootl.left
、root.right
“接住” 上层递归的返回值
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return null;
}
// 后序
TreeNode leftRoot = mirrorTree(root.right);
TreeNode rightRoot = mirrorTree(root.left);
// 交换左、右孩子
root.left = leftRoot;
root.right = rightRoot;
return root;
}
}
以上代码,可以精简,K佬题解 精简为:
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) return null;
// 必须要保存之前的left
TreeNode temp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
}
注意:每次递归前,必须要保存之前的
left
迭代版本
前序 dfs
递归本质也是用栈,这里直接用 栈
来实现“前序”遍历。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) { // 保证stack中不会有null节点
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
// 前序
// 交换左、右孩子
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
// 栈是FILO的。进栈时,先右后左;则出栈时,先左后右
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return root;
}
}
层次遍历 bfs
利用队列实现层次遍历,仍然在遍历过程中“交换所有节点的左、右孩子”
class Solution {
public TreeNode mirrorTree(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
// 交换左右孩子
TreeNode temp = node.left;
node.left = node.right;
node.right =temp;
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
return root;
}
}
剑指 Offer 28. 对称的二叉树
递归 ⭐️
参考:三叶题解
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return check(root.left, root.right);
}
boolean check(TreeNode left, TreeNode right) {
// 1)左右子树都是空,说明以父节点为根节点的树为“对称二叉树”
if (left == null && right == null) return true;
// 2)左右子树,有一个为空、另一个不为空,说明以父节点为根节点的树不是 “对称二叉树”
if (left == null || right == null) return false;
// 左右子树的节点值不相等,说明以父节点为根节点的树不是 “对称二叉树”
if (left.val != right.val) return false;
// 递归判断“左孩子的左孩子和右孩子的右孩子”,以及“左孩子的右孩子和右孩子的左孩子”是否为 对称二叉树
return check(left.left, right.right) && check(left.right, right.left);
}
}
层次遍历
局部检查
思路
- 根据层次遍历,收集每一层的节点(包含
null
节点); - 使用
双指针
检查每层的节点是否对称
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> cntLayer = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
cntLayer.add(node == null ? 127 : node.val);
// 即使左右孩子为null,也要将其加入到队列中
if (node != null) {
queue.offer(node.left);
queue.offer(node.right);
}
}
System.out.println(cntLayer);
// 判断当前层是否对称
for (int left = 0, right = cntLayer.size() - 1; left <= right
; left++, right--) {
// if (cntLayer.get(left) != cntLayer.get(right)) {
// 这里一定用 equals,而不是用 ==
if (!cntLayer.get(left).equals(cntLayer.get(right))) {
System.out.println("left = " + left);
System.out.println("right = " + right);
return false;
}
}
}
return true;
}
}
整体检查 bfs
⭐️
bfs
使用队列实现“递归-整体检查” 的迭代版本。
思路:和上面“递归”版本一致。
注意:这里一次要同时入队
left
、right
两个节点。出队时,也是同时出队两个节点。
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()) {
TreeNode leftChild = queue.poll();
TreeNode rightChild = queue.poll();
// 左右子树均为空,则跳过(说明以当前父节点为根的子树为对称的)
if (leftChild == null && rightChild == null) {
// return true;
continue;
}
// 如果左、右子树,一个为空、另一个不为空,则不对称
if (leftChild == null || rightChild == null) {
return false;
}
// 如果左、右子树 数值不相等、则不对称
if (leftChild.val != rightChild.val) {
return false;
}
// 否则,比较“左孩子的左孩子和右孩子的右孩子”、以及“左孩子的右孩子和右孩子的左孩子”
queue.offer(leftChild.left);
queue.offer(rightChild.right);
queue.offer(leftChild.right);
queue.offer(rightChild.left);
}
return true;
}
}