目录
搜索与回溯的碰撞
1 树的子结构
剑指 Offer 26. 树的子结构https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/
一开始的思路是求出两个二叉树的一个先序遍历序列,List转换为字符串,再用contain。第47例子过不了。
看大佬思路很简洁~又是来人间混日子的一天!
若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:
- 先序遍历树 A 中的每个节点 nA ;(对应函数
isSubStructure(A, B)
) - 判断树 A 中 以 nA 为根节点的子树 是否包含树 B 。(对应函数
recur(A, B)
)
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
}
boolean recur(TreeNode A, TreeNode B) {
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
recur(A, B)
函数:
- 终止条件:
- 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
- 当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false ;
- 当节点 A 和 B 的值不同:说明匹配失败,返回 false ;
- 返回值:
- 判断 A 和 B 的左子节点是否相等,即
recur(A.left, B.left)
; - 判断 A 和 B 的右子节点是否相等,即
recur(A.right, B.right)
;
- 判断 A 和 B 的左子节点是否相等,即
isSubStructure(A, B)
函数:
- 特例处理: 当 树 A 为空 或 树 B 为空 时,直接返回 false ;
- 返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或
||
连接;- 以 节点 A 为根节点的子树 包含树 B ,对应
recur(A, B)
; - 树 B 是 树 A 左子树 的子结构,对应
isSubStructure(A.left, B)
; - 树 B 是 树 A 右子树 的子结构,对应
isSubStructure(A.right, B)
;
以上
2.
3.
实质上是在对树 AA 做 先序遍历 。 - 以 节点 A 为根节点的子树 包含树 B ,对应
复杂度分析:
- 时间复杂度 O(MN) : 其中M,N 分别为树 A 和 树 B 的节点数量;先序遍历树 A 占用 O(M) ,每次调用
recur(A, B)
判断占用 O(N) 。 - 空间复杂度 O(M) : 当树 A 和树 B 都退化为链表时,递归调用深度最大。当M≤N 时,遍历树 AA 与递归判断的总递归深度为 M ;当 M>N 时,最差情况为遍历至树 A 叶子节点,此时总递归深度为 M。
2 镜像二叉树
剑指 Offer 27. 二叉树的镜像https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/
1. 递归
终止条件:左右子树都是空返回构造的当前节点。
package jzof.Day07;
import jzof.TreeNode;
/**
* @author ahan
* @create_time 2021-11-10-8:52 上午
*/
public class _27 {
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
TreeNode node1 = new TreeNode(4);
TreeNode node2 = new TreeNode(5);
TreeNode node3 = new TreeNode(1);
TreeNode node4 = new TreeNode(2);
root.left = node1;
root.right = node2;
node1.left = node3;
node1.right = node4;
TreeNode _root = new _27().mirrorTree(root);
System.out.println(root.val);
System.out.println(root.left.val);
System.out.println(root.right.val);
}
public TreeNode mirrorTree(TreeNode root) {
if(root == null){
return null;
}
TreeNode _root = new TreeNode(root.val);
TreeNode _right = null;
TreeNode _left = null;
if (root.left != null){
_right = mirrorTree(root.left);
}
if (root.right != null){
_left = mirrorTree(root.right);
}
_root.right = _right;
_root.left = _left;
return _root;
}
}
简化代码~在原来的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) {
if(root == null)
return null;
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.right);
root.left = right;
root.right = left;
return 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) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
复杂度分析:
- 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
- 空间复杂度 O(N) : 最差情况下(当二叉树退化为链表),递归时系统需使用 O(N) 大小的栈空间。
2. 辅助栈(或队列)
- 利用栈(或队列)遍历树的所有节点 node ,并交换每个 node 的左 / 右子节点。
算法流程:
- 特例处理: 当 root 为空时,直接返回 null ;
- 初始化: 栈(或队列),本文用栈,并加入根节点 root 。
- 循环交换: 当栈 stack 为空时跳出;
- 出栈: 记为 node ;
- 添加子节点: 将 noode 左和右子节点入栈;
- 交换: 交换 node 的左 / 右子节点。
- 返回值: 返回根节点 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) {
if(root == null) return null;
Stack<TreeNode> stack = new Stack<>() {{ add(root); }};
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
if(node.left != null) stack.add(node.left);
if(node.right != null) stack.add(node.right);
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
}
return root;
}
}
复杂度分析:
- 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
- 空间复杂度 O(N) : 如下图所示,最差情况下,栈 stack 最多同时存储 2N+1 / 2个节点,占用 O(N) 额外空间。
3 对称二叉树
剑指 Offer 28. 对称的二叉树https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/
1. 比较镜像二叉树
- 第一步 判断 节点值是否相等 若是不相等直接return false;
- 第二步 判断当前节点左子树或右子树是否相同
- 是同时为null 则该侧情况为true
- 是同时为非null 递归调用函数
- 第三步 根据左右两边情况返回 对当前节点的判断
package jzof.Day07;
import jzof.TreeNode;
/**
* @author ahan
* @create_time 2021-11-10-9:51 上午
*/
public class _28 {
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
TreeNode node1 = new TreeNode(4);
TreeNode node2 = new TreeNode(5);
TreeNode node3 = new TreeNode(1);
TreeNode node4 = new TreeNode(2);
root.left = node1;
root.right = node2;
node1.left = node3;
node1.right = node4;
TreeNode root2 = new TreeNode(1);
TreeNode node12 = new TreeNode(2);
TreeNode node22 = new TreeNode(2);
TreeNode node32 = new TreeNode(3);
TreeNode node42 = new TreeNode(4);
TreeNode node52 = new TreeNode(4);
TreeNode node62 = new TreeNode(3);
root2.left = node12;
root2.right = node22;
node12.left = node32;
node12.right = node42;
node22.left = node52;
node22.right = node62;
System.out.println(new _28().isSymmetric(root2));
}
public boolean isSymmetric(TreeNode root) {
return root != null ? isSub(root, mirrorTree(root)) : true;
}
public boolean isSub(TreeNode root, TreeNode _root){
boolean right = false;
boolean left = false;
if(_root.val != root.val){
return false;
}
if (root.right == null && _root.right == null)
right = true;
if (root.right != null && _root.right != null)
right = isSub(root.right, _root.right);
if (root.left == null && _root.left == null)
left = true;
if (root.left != null && _root.left != null)
left = isSub(root.left, _root.left);
return left && right;
}
public TreeNode mirrorTree(TreeNode root) {
if(root == null){
return null;
}
TreeNode _root = new TreeNode(root.val);
TreeNode _right = mirrorTree(root.left);
TreeNode _left = mirrorTree(root.right);
_root.right = _right;
_root.left = _left;
return _root;
}
// 在原二叉树上修改了
public TreeNode mirrorTree_1(TreeNode root) {
if(root == null)
return null;
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
在处理递归的写法不好! 根据大佬的写法改写如下:
先判断是否为皆是null 返回true
再判断有一个为null 或者 val 不同情况 返回false
最后根据左右递归判断左右子树是否符合 返回最终结果。
public boolean isSub(TreeNode root, TreeNode _root){
if(root == null && _root == null) return true;
if(root == null || _root == null || root.val != _root.val) return false;
return isSub(root.left, _root.left) && isSub(root.right, _root.right);
}
资质愚钝,没想到直接比较左右子树~
2. 比较左右子树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
public boolean recur(TreeNode L, TreeNode R){
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
复杂度分析:
- 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,每次执行
recur()
可以判断一对节点是否对称,因此最多调用 N/2 次recur()
方法。 - 空间复杂度 O(N) : 最差情况下(见下图),二叉树退化为链表,系统使用 O(N) 大小的栈空间。