您的当前位置:首页正文

剑指offer_面试题27:二叉树镜像,面试题28:对称的二叉树,面试题29:顺时针打印矩阵,最大公约数和最小公倍数,面试题30:包含min函数的栈,面试题31:栈的压入、弹出序列

2024-11-28 来源:个人技术集锦
面试题27:

  • 注意: 该题让求一棵二叉树的镜像,而不是判断两棵树是否为镜像对称。
  • 分析:
  • 代码如下:
public TreeNode invertTree(TreeNode root) {
    // root为null的特殊情况无需处理
    prevOrder(root);
    return root;
}

// 先序遍历中插入交换左右孩子节点的操作,实现镜像构造
public void prevOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;
    prevOrder(root.left);
    prevOrder(root.right);
}
面试题28:

  • 如果一颗二叉树关于自身对称,则以根节点为中心,左边的等于右边的,右边等于左边的。
① 比较左右子树
  1. 除去根节点,其他节点以根节点为轴对称:左孩子等于右孩子,右孩子等于左孩子。
  2. 相当于判断二叉树的左右子树是否镜像对称,在进行子树是否相同的基础上,只需要更改判断的对象就可以实现。
  • 代码如下:
public boolean isSymmetric(TreeNode root) {
    // 要判断左右子树,需要处理root为null的情况
    if (root==null){
        return true;
    }
    return isDuichen(root.left, root.right);
}

// 传入左右子树,递归判断是否镜像对称
public boolean isDuichen(TreeNode node1, TreeNode node2) {
    if (node1 == null && node2 == null) {
        return true;
    }
    if (node1 == null || node2 == null) {
        return false;
    }
    return (node1.val == node2.val) && isDuichen(node1.left, node2.right) &&
            isDuichen(node1.right, node2.left);
}
② 两个自己进行比较
  • 如果一棵二叉树关于自身对称,两个它关于中间成镜像对称。因此,可以在判断是否对称时,直接传入两个自己。
  • 代码如下:
public boolean isSymmetric(TreeNode root) {
    // 直接传入两个自己,不用判断根节点为null的特殊情况
    return isDuichen(root,root);
}
③ 迭代实现
  1. 利用两个stack,实现对左右子树的节点的存储。
  2. 压入左右子树的孩子节点时,按照左孩子/右孩子、右孩子/左孩子的顺序压入。
  • 代码如下:
public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return true;
    }
    Stack<TreeNode> s1 = new Stack<>();
    Stack<TreeNode> s2 = new Stack<>();
    s1.push(root.left);
    s2.push(root.right);
    while (!s1.isEmpty() && !s2.isEmpty()) {
        TreeNode left = s1.pop();
        TreeNode right = s2.pop();
        if (left == null && right == null) {
            continue;
        }
        if (left == null || right == null) {
            return false;
        }
        if (left.val != right.val) {
            return false;
        }
        s1.push(left.left);
        s2.push(right.right);
        s1.push(left.right);
        s2.push(right.left);
    }
    return true;
}
  • 也可以。
面试题29:

  • 矩阵每次打印都是由上方的r1,下方的r2,左边的c1,右边的c2去共同控制的。
  • 注意: 当从右到左、从下到上的打印之前,需要先判断是否可以停止打印。
  • 代码如下:
public List<Integer> spiralOrder(int[][] matrix) {
    List<Integer> list = new ArrayList<>();
    if (matrix.length == 0 || matrix[0].length == 0) {
        return list;
    }
    int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
    while (r1 <= r2 && c1 <= c2) {
        // 从左向右打印,对应行为r1,起始点为c1,一直到c2
        for (int i = c1; i <= c2; i++) {
            list.add(matrix[r1][i]);
        }
        // 从上到下打印,对应列为c2,起始点为r1+1,一直到r2
        for (int i = r1 + 1; i <= r2; i++) {
            list.add(matrix[i][c2]);
        }
        // 没打印完,从右到左打印,对应行为r2,从c2-1开始,一直到c1
        if (r1 != r2) {
            for (int i = c2 - 1; i >= c1; i--) {
                list.add(matrix[r2][i]);
            }
        }
        // 没打印完,从下到上打印,对应列为c1,从r2-1开始,一直到r1-1
        if (c1 != c2) {
            for (int i = r2 - 1; i > r1; i--) {
                list.add(matrix[i][c1]);
            }
        }
        // 一圈打印完,更新下标
        r1++;
        r2--;
        c1++;
        c2--;
    }
    return list;
}
最大公约数和最小公倍数
  • 利用辗转相除法求最大公约数:
  1. (319,377)为例,交换二者的位置,让较大数做被除数。
  2. 58 = 377 % 319,则有(319,58)
  3. 29 = 319 % 58,则有(58,29)
  4. 0 = 58 % 29,则最大公约数为29
  • 代码如下:
public int GCD(int a, int b) {
    int max = Math.max(a, b);
    int min = Math.min(a, b);
    while (min != 0) {
        int temp = max % min;
        max = min;
        min = temp;
    }
    // 在while循环中已经更新了max为除数,因此要返回max
    return max;
}
  • 求到最大公约数后,将两个数的乘积除以最大公约数,就是最小公倍数:a* b/ GCD(a, b)
  • 代码如下:
public static  int LCM(int a, int b) {
    int max = Math.max(a, b);
    int min = Math.min(a, b);
    while (min != 0) {
        int temp = max % min;
        max = min;
        min = temp;
    }
    // 原始数的乘积除以计算出的最大公约数,就是最小公倍数
    return a * b / max;
}
面试题30:

  • 自己的native想法:
  1. 要想调用min函数时,时间复杂度为 O ( 1 ) O(1) O(1),直接使用一个min变量存储栈中的最小值。
  2. 比如压入3,2,1,最小值为1,如果弹出了1,那最小值应该是栈中的2还是3呢?
  3. 这时,如果查找栈中min,时间复杂度就不是 O ( 1 ) O(1) O(1)了。
  • 以空间复杂度换取时间复杂度:
  1. 创建一个存储当前栈中最小元素的辅助栈,每次获取min时,直接获取辅助栈栈顶元素即可。
  2. 数据栈弹出一个元素,辅助栈也需要弹出对应的元素。
  3. 辅助栈压入的元素为:min(数据栈待压入元素,辅助栈的栈顶元素)
  • 代码如下:
class MinStack {
    private Stack<Integer> data;
    private Stack<Integer> helper;
    
    public MinStack() {
        data = new Stack<>();
        helper = new Stack<>();
    }

    public void push(int x) {
        data.push(x);
        // 辅助栈为空,最小值是当前元素;否则,需要进行比较
        int min = (helper.isEmpty()) ? x : Math.min(x, helper.peek());
        helper.push(min);
    }

    public void pop() {
        helper.pop();
        data.pop();
    }

    public int top() {
        return data.peek();
    }

    public int getMin() {
        return helper.peek();
    }
}
面试题31:栈的压入、弹出序列

  • 对于压入序列,如果与弹出序列不相同,则直接压入栈;如果待压入的数字是下一个弹出的数字,则不压入数字,直接逃过。
  • 编程思路:
  1. 先压入数字,在将匹配的栈顶元素弹出栈。
  2. 如果最后栈不为空,说明不是弹出序列;否则,是弹出序列。
  3. 无需进行空数组判断,如果数组为空不会进行for循环。
  • 代码如下:
public boolean validateStackSequences(int[] pushed, int[] popped) {
    Stack<Integer> stack = new Stack<>();
    int cur = 0;// 指向待弹出的数字
    for (int i = 0; i < pushed.length; i++) {
        // 先将数字压入栈
        stack.push(pushed[i]);
        // 与下一个弹出数字作比较,栈顶元素相同则弹出
        while (!stack.isEmpty()) {
            if (stack.peek() == popped[cur]) {
                stack.pop();
                cur++;
            } else {
                break;
            }
        }
    }
    return stack.isEmpty();
}
显示全文