六、剑指 Offer(25~29)


一、25 合并两个排序的链表

1.算法描述

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

2.算法题解

根据链表 l1、l2 是递增的,因此可以使用双指针遍历链表,根据 val1、val2 的大小关系确定节点添加顺序,两指针交替前进,直至遍历完成。

由于初始状态合并链表中无节点,因此需要初始化一个辅助节点 root 作为合并链表的伪头结点。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode root = new ListNode(0), cur = root;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 != null ? l1 : l2;
        return root.next;
    }
}

二、26 树的子结构

1.算法描述

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

2.算法题解

若树 B 是树 A 的子结构,则子结构的根节点为树 A 的任意一个节点,因此判断树 B 是否是树 A 的子结构需要进行一下步骤:

  • 遍历树 A 的每个节点 node(函数 isSubStructure)
  • 判断树 A 中以 node 为根节点的子树是否包含树 B(函数 check)

isSubStructure(A, B) 函数:

特例处理:当 树 A 为空或树 B 为空时,直接返回 false。

若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接:

  • 以节点 A 为根节点的子树包含树 B,对应 check(A, B)
  • 树 B 是树 A 左子树的子结构,对应 isSubStructure(A.left, B)
  • 树 B 是树 A 右子树的子结构,对应 isSubStructure(A.right, B)
  • 最后两个步骤实质上是在对树 A 做先序遍历

check(A, B) 函数:

  • 当节点 B 为空时,说明树 B 已匹配完成,因此返回 true
  • 当节点 A 为空时,说明已经越过树 A 叶子节点,即匹配失败返回 false
  • 当节点 A 和 B 的值不同时,说明匹配失败,返回 false
  • 当节点 A 等于节点 B 时,判断 A 和 B 的左子节点和右子节点是否相等,即 check(A.left, B.left) 和 check(A.right, B.right)
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        return (A != null && B != null) && (check(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
    }

    public boolean check(TreeNode A, TreeNode B) {
        if (B == null) {
            return true;
        }
        if (A == null || A.val != B.val) {
            return false;
        }
        return check(A.left, B.left) && check(A.right, B.right);
    }
}

三、27 二叉树的镜像

1.算法描述

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

2.算法题解

递归法:根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。

  • 初始化节点 tmp ,用于暂存 root 的左子节点。
  • 递归右子节点 mirrorTree(root.right),并将返回值作为 root 的左子节点。
  • 递归左子节点 mirrorTree(tmp),并将返回值作为 root 的右子节点。
  • 返回 root 节点。
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;
    }
}

辅助栈:利用栈遍历(bfs)树的所有节点 node,并交换每个 node 的左 / 右子节点。

  • 特例处理:当 root 为空时,直接返回 null
  • 初始化栈:加入根节点 root
  • 循环交换:当栈 stack 为空时跳出
  • 出栈:记为 node
  • 添加子节点:将 node 的左和右子节点入栈
  • 交换节点:交换 node 的左 / 右子节点
  • 返回值: 返回根节点 root
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;
    }
}

四、28 对称的二叉树

1.算法描述

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

2.算法题解

对称二叉树定义:对于树中任意两个对称节点 L 和 R 一定有:

  • L.val = R.val :即此两对称节点值相等。
  • L.left.val = R.right.val 即 L 的左子节点和 R 的右子节点对称
  • L.right.val = R.left.val 即 L 的右子节点和 R 的左子节点对称
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return root == null ? true : check(root.left, root.right);
    }

    public boolean check(TreeNode L, TreeNode R) {
        if (L == null && R == null) {
            return true;
        }
        if (L == null || R == null || L.val != R.val) {
            return false;
        }
        return check(L.left, R.right) && check(L.right, R.left);
    }
}

五、29 顺时针打印矩阵

1.算法描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

2.算法题解

根据题意可知,顺时针打印的顺序是:从左到右,从上到下,从右到左,从下到上

  • 当 matrix 为空时,直接返回空数组。
  • 创建返回数组,并定义返回数组的下标 index = 0
  • 定义边界上下左右边界 top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1
  • 按照 “从左向右、从上向下、从右向左、从下向上” 四个方向循环,每个方向打印中做以下三件事:
  1. 根据边界打印,即将元素按顺序添加至列表 res 尾部
  2. 边界向内收缩 1 代表已被打印
  3. 判断是否打印完毕,边界是否相遇,若打印完毕则跳出
打印方向打印边界边界收缩是否打印完毕
从左到右左边界 left,右边界 right上边界 top 加 1top > bottom
从上到下上边界 top,下边界 bottom右边界 right 减 1left > right
从右到左右边界 right,左边界 left下边界 bottom 加 1top > bottom
从下到上下边界 bottom,上边界 top左边界 left 加 1left > right
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix.length == 0) {
            return new int[0];
        }
        int index = 0;
        int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1;
        int[] result = new int[matrix[0].length * matrix.length];
        while (true) {
            for (int i = left; i <= right; i++) {
                result[index++] = matrix[top][i]; // left ---> right
            }
            if (++top > bottom) {
                break;
            }
            for (int i = top; i <= bottom; i++) {
                result[index++] = matrix[i][right]; // top ---> bottom
            }
            if (left > --right) {
                break;
            }
            for (int i = right; i >= left; i--) {
                result[index++] = matrix[bottom][i]; // right ---> left
            }
            if (top > --bottom) {
                break;
            }
            for (int i = bottom; i >= top; i--) {
                result[index++] = matrix[i][left]; // bottom ---> top
            }
            if (++left > right) {
                break;
            }
        }
        return result;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值