《剑指offer》 -day7-搜索与回溯算法(简单)

剑指 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.leftroot.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);
    }
}

层次遍历

局部检查

思路

  1. 根据层次遍历,收集每一层的节点(包含null节点);
  2. 使用双指针检查每层的节点是否对称
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 使用队列实现“递归-整体检查” 的迭代版本。

思路:和上面“递归”版本一致。

注意:这里一次要同时入队leftright两个节点。出队时,也是同时出队两个节点。

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值