【刷爆力扣之101.对称二叉树-100.相同的树】

101.对称二叉树

1.递归法

递归三部曲

  1. 确定递归函数的参数和返回值

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

返回值自然是bool类型。

代码如下:

private boolean doIsSymmetric(TreeNode left, TreeNode right) {
  1. 确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点

  • 左节点为空,右节点不为空,不对称,return false
  • 左不为空,右为空,不对称 return false
  • 左右都为空,对称,返回true

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

  • 左右都不为空,比较节点数值,不相同就return false

此时左右节点不为空,且数值也不相同的情况我们也处理了。

代码如下:

// 若同时为 null
if (left == null && right == null) {
    return true;
}
// 若有一个为 null (有上一轮筛选,另一个肯定不为 null)
if (left == null || right == null) {
    return false;
}
// 若两节点值不相等,返回false
if (!Objects.equals(left.val, right.val)) {
    return false;
}
  1. 确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回true ,有一侧不对称就返回false 。

代码如下:

boolean a = doIsSymmetric(left.left, right.right);
boolean b = doIsSymmetric(left.right, right.left);
return a && b;

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。

最后递归的Java整体代码如下:

public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return false;
    }
    return doIsSymmetric(root, root);
}

private boolean doIsSymmetric(TreeNode left, TreeNode right) {
    // 若同时为 null
    if (left == null && right == null) {
        return true;
    }
    // 若有一个为 null (有上一轮筛选,另一个肯定不为 null)
    if (left == null || right == null) {
        return false;
    }
    // 若两节点值不相等,返回false
    if (!Objects.equals(left.val, right.val)) {
        return false;
    }
    boolean a = doIsSymmetric(left.left, right.right);
    boolean b = doIsSymmetric(left.right, right.left);
    return a && b;
}

2.迭代法

这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。

这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历

使用队列

如下的条件判断和递归的逻辑是一样的。

代码如下:

/**
     * 迭代法
     * 使用普通队列
     */
public boolean isSymmetric3(TreeNode root) {
    Queue<TreeNode> deque = new LinkedList<>();
    deque.offer(root.left);
    deque.offer(root.right);
    while (!deque.isEmpty()) {
        TreeNode leftNode = deque.poll();
        TreeNode rightNode = deque.poll();
        if (leftNode == null && rightNode == null) {
            continue;
        }
        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }
        // 这里顺序与使用Deque不同
        deque.offer(leftNode.left);
        deque.offer(rightNode.right);
        deque.offer(leftNode.right);
        deque.offer(rightNode.left);
    }
    return true;
}

使用栈

细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。

/**
     * 迭代法
     * 使用双端队列,相当于两个栈
     */
public boolean isSymmetric2(TreeNode root) {
    Deque<TreeNode> deque = new LinkedList<>();
    deque.offerFirst(root.left);
    deque.offerLast(root.right);
    while (!deque.isEmpty()) {
        TreeNode leftNode = deque.pollFirst();
        TreeNode rightNode = deque.pollLast();
        if (leftNode == null && rightNode == null) {
            continue;
        }
        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }
        deque.offerFirst(leftNode.left);
        deque.offerFirst(leftNode.right);
        deque.offerLast(rightNode.right);
        deque.offerLast(rightNode.left);
    }
    return true;
}

100.相同的树 

这道题和上面的101对称二叉树本质上是同一道题,都是判断两颗二叉树的各个位置的节点是否相同,对称二叉树主要看同一颗树的左右两颗子树是否是相互对称的,因此需要判断左子树的内侧节点以及外侧节点与右子树的内侧节点与外侧节点是否相同,而本题主要判断两颗树相同位置的节点是否相同,本质上都是一样的,思路上也有很多相同之处。

本地依然可以使用递归和迭代两种方式解题:

递归:

依然是熟悉的递归三部曲:

  1. 判断递归的参数和返回值:
    由于本题需要判断两颗树是否相同,因此需要在递归中遍历到两颗树的所有节点,故递归的参数需要两颗树的根节点,返回值就是bool类型
public boolean isSameTree(TreeNode p, TreeNode q) {
  1. 判断递归的终止条件:

递归的终止条件有三种:

    • 遍历到的两颗树的节点都为null,返回true。
    • 遍历到的两颗树的节点只有一个为null,返回false。
    • 遍历到的两颗树的节点都不为null,但是两颗树的节点的值不同,返回false。
if (p == null && q == null) {
    return true;
}
// 通过上面的判断,这个判断如果为true一定是两个节点一个为null一个不为null,返回false
if (p == null || q == null) {
    return false;
}
if (p.val != q.val) {
    return false;
}
  1. 判断单次递归的逻辑:

单次递归的逻辑只有在两个节点都不为null,并且两个节点的值相同的情况下才会执行,因此单次递归的逻辑就是继续比较两个节点的子节点是否相同,这里也是和101.对称二叉树 解法不同的地方,因为101题强调两颗树的对称,因此在比较子节点时,是将左子树的左节点和右子树的右节点比较,即比较子树的外侧,以及左子树的右节点和右子树的左节点,即比较子树的内侧,而本题是比较两颗树是否相同,因此应该比较相同位置的节点是否相同,因此应该比较两个节点的左节点是否相同以及两个节点的右节点是否相同。

// 判断两个节点的左子树是否是相同的
boolean a = isSameTree(p.left, q.left);
// 判断两个节点的右子树是否是相同的
boolean b = isSameTree(p.right, q.right);
return a && b;

完整的递归代码如下:

// 递归
public boolean isSameTree(TreeNode p, TreeNode q) {
    if (p == null && q == null) {
        return true;
    }
    // 通过上面的判断,这个判断如果为true一定是两个节点一个为null一个不为null,返回false
    if (p == null || q == null) {
        return false;
    }
    if (p.val != q.val) {
        return false;
    }
    // 判断左子树是否是相同的
    boolean a = isSameTree(p.left, q.left);
    // 判断右子树是否是相同的
    boolean b = isSameTree(p.right, q.right);
    return a && b;
}

迭代:

迭代法用到了与101.对称二叉树相同的思想,每次将下次需要比较的两个节点依次加入到一个队列数据结构,如果队列不为空,则拿出队列中的两个元素进行判断和比较,具体的判断逻辑和上述递归方法中的第二个步骤相同,因此直接给出代码实现:

注意:

入队的顺序必须是两个节点比较的顺序,并且两个需要比较的节点必须相邻入队。

//迭代
    public boolean isSameTree2(TreeNode p, TreeNode q) {
        // 队列
        Queue<TreeNode> queue = new LinkedList<>();
        // 需要比较的元素入队,第一次入队也可以叫为初始化队列
        queue.offer(p);
        queue.offer(q);
        while (!queue.isEmpty()) {
            // 弹出需要比较的两个节点进行比较
            TreeNode pNode = queue.poll();
            TreeNode qNode = queue.poll();
            if (pNode == null && qNode == null) {
                continue; // 都为null,跳出本次循环,比较队列中其他节点
            }
            if (pNode == null || qNode == null || qNode.val != pNode.val) {
                return false;
            }
            // 将需要比较的两个节点依次放入队列
            queue.offer(pNode.left);
            queue.offer(qNode.left);
            queue.offer(pNode.right);
            queue.offer(qNode.right);
        }
        return true;
    }
  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值