阿翰 剑指offer 之 Day 07 搜索与回溯的碰撞 2

目录

搜索与回溯的碰撞

1 树的子结构

2 镜像二叉树

1. 递归

2.  辅助栈(或队列)

3 对称二叉树

1. 比较镜像二叉树

2. 比较左右子树


搜索与回溯的碰撞

1 树的子结构

剑指 Offer 26. 树的子结构https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/

一开始的思路是求出两个二叉树的一个先序遍历序列,List转换为字符串,再用contain。第47例子过不了。

看大佬思路很简洁~又是来人间混日子的一天!

若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:

  1. 先序遍历树 A 中的每个节点 nA​ ;(对应函数 isSubStructure(A, B)
  2. 判断树 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) 函数:

  1. 终止条件:
    1. 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
    2. 当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false ;
    3. 当节点 A  和 B 的值不同:说明匹配失败,返回 false ;
  2. 返回值:
    1. 判断 A 和 B 的子节点是否相等,即 recur(A.left, B.left) ;
    2. 判断 A 和 B 的子节点是否相等,即 recur(A.right, B.right) ;

isSubStructure(A, B) 函数:

  1. 特例处理: 当 树 A 为空  树 B 为空 时,直接返回 false ;
  2. 返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
    1. 以 节点 A 为根节点的子树 包含树 B ,对应 recur(A, B)
    2. 树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B)
    3. 树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B)

    以上 2. 3. 实质上是在对树 AA 做 先序遍历 。

复杂度分析:

  • 时间复杂度 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 的左 / 右子节点。

算法流程:

  1. 特例处理: 当 root 为空时,直接返回 null ;
  2. 初始化: 栈(或队列),本文用栈,并加入根节点 root 。
  3. 循环交换: 当栈 stack 为空时跳出;
    1. 出栈: 记为 node ;
    2. 添加子节点: 将 noode 左和右子节点入栈;
    3. 交换: 交换 node 的左 / 右子节点。
  4. 返回值: 返回根节点 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; 
    }  
}

复杂度分析:

  1. 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
  2. 空间复杂度 O(N) : 如下图所示,最差情况下,栈 stack 最多同时存储 2N+1​ / 2个节点,占用 O(N) 额外空间。

3 对称二叉树

剑指 Offer 28. 对称的二叉树https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/

1. 比较镜像二叉树

  1. 第一步 判断 节点值是否相等 若是不相等直接return false;
  2. 第二步 判断当前节点左子树或右子树是否相同
    1. 是同时为null 则该侧情况为true
    2. 是同时为非null 递归调用函数
  3. 第三步 根据左右两边情况返回 对当前节点的判断
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) 大小的栈空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值