《剑指offer》Java实现——每天9题——第4天

面试题28 对称的二叉树

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

测试用例
  • 功能测试(对称的树;因结构而不对称的二叉树;结构对称但节点值不对称的二叉树)
  • 特殊值测试(树为null;树只有一个根节点;所有节点值都相同的树)
实现代码
/**
     * 入口方法,负责进行参数校验和递归调用
     * @param root 树
     * @return 是否对称
     */
    public static boolean isSymmetrical(BinaryTreeNode root) {
        return root == null || isSymmetrical(root, root);
    }

    /**
     * 核心递归方法,进行前序遍历和对称前序遍历对应位置的比较
     * @param tree1 root树
     * @param tree2 root树
     * @return true 对称 or false 不对称
     */
    private static boolean isSymmetrical(BinaryTreeNode tree1, BinaryTreeNode tree2){
        if (tree1 == null && tree2 == null)
            return true;
        if (tree1 == null || tree2 == null)
            return false;
        if (tree1.nValue != tree2.nValue)
            return false;
        return isSymmetrical(tree1.left,tree2.right) && isSymmetrical(tree1.right,tree2.left);
    }
算法思路

树的3种遍历方法均是先遍历左孩子,再遍历右孩子。以前序遍历为例,考虑一种新的对称的前序遍历方法,先遍历右孩子,再遍历左孩子。如果两棵树对称,那么前序遍历和对称前序遍历对应位置上的节点值相同(包括null)。使用递归可以很简洁地实现。

面试题29 顺时针打印矩阵

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

测试用例
  • 功能测试(正常矩阵;矩阵只有一个元素;矩阵只有行或列)
  • 特殊值测试(矩阵为null)
实现代码
/**
     * 打印矩阵的接口方法,是递归方法的入口
     * @param matrix 要打印的矩阵
     */
    public void printMatrix(int[][] matrix){
        if (matrix == null)
            return;
        int row = 0,col = 0;
        int rows = matrix.length,cols = matrix[0].length;

        printCore(matrix,row,col,rows,cols);
    }

    /**
     * 打印矩阵的核心递归方法
     * @param matrix 矩阵
     * @param row 打印起始的起始行
     * @param col 打印起始的起始列
     * @param rows 还没打印的行数
     * @param cols 还没打印的列数
     */
    private void printCore(int[][] matrix, int row, int col, int rows, int cols) {
        if (rows < 1 || cols<1)
            return;
        //第一步,打印一行
        for (int i =0;i<cols;i++)
            System.out.print(matrix[row][col + i] + " ");

        //第二步,需要条件rows>1
        if (rows>1){
            for (int j =1;j<rows;j++)
                System.out.print(matrix[row+j][col+cols-1]+" ");
        }
        //第三步,需要条件中间的矩阵至少要有两行两列
        if (rows>=2 && cols>=2){
            for (int m =1;m<cols;m++)
                System.out.print(matrix[row+rows-1][col+cols-1-m]+" ");
        }

        //第四步,至少有3行2列
        if (rows>=3 && cols>=2){
            for (int n=1;n<=rows-2;n++)
                System.out.print(matrix[row+rows-1-n][col]+" ");
        }
        printCore(matrix,row+1,col+1,rows-2,cols-2);
    }
    //测试方法
    public static void main(String[] args) {
        InterQuestions29 test = new InterQuestions29();
        int[][] matrix0 = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
        int[][] matrix1 = {{1,2,3,4},{5,6,7,8}};
        int[][] matrix2 = {{1}};
        int[][] matrix3 = {{}};
        int[][] matrix4 = null;
        System.out.println("mateix0:");
        test.printMatrix(matrix0);
        System.out.println("mateix1:");
        test.printMatrix(matrix1);
        System.out.println("mateix2:");
        test.printMatrix(matrix2);
        System.out.println("mateix3:");
        test.printMatrix(matrix3);
        System.out.println("mateix4:");
        test.printMatrix(matrix4);
    }
算法思路

首先打印最外围的一圈,记录起始坐标,未打印的行数、列数,递归进行。每一次打印分为四步。第一步,打印第一行,这一步总是必须的;第二步,打印最后一列,只有当列数大于1时才进行;第三步,打印最后一行,只有当前矩阵至少有两行两列时才进行(两行一列的情形已经在第二部处理了);第四部,打印第一列,只有当前矩阵至少有3行2列才进行。

面试题30 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。

测试样例
  • 功能测试(栈为空时插入;新压入的数字比之前的最小值大或者小;弹出栈的不是最小元素;弹出栈的是最小元素)
  • 特殊测试(栈为空时弹出)
实现代码
/**
 * 包含min函数的栈
 */
public  class InterQuestions30<AnyType extends Comparable<? super AnyType>> {
    private Stack<AnyType> stack = new Stack<>();
    private Stack<AnyType> assistStack = new Stack<>();

    public void push(AnyType data){
        if (stack.isEmpty() || data.compareTo(assistStack.peek())<0) {
            stack.push(data);
            assistStack.push(data);
        } else {
            stack.push(data);
            assistStack.push(assistStack.peek());
        }
    }

    public AnyType pop(){
        if (!stack.empty()) {
            assistStack.pop();
            return stack.pop();
        }
        return null;
    }

    public AnyType min(){
        return assistStack.peek();
    }

    public static void main(String[] args) {
        InterQuestions30<Integer> myStack = new InterQuestions30<>();
        myStack.push(3);
        myStack.push(4);
        myStack.push(2);
        myStack.push(1);
        System.out.println(myStack.pop());
        System.out.println(myStack.pop());
        myStack.push(0);
    }
}
算法思路

使用一个辅助栈记录最小值。当压入的值小于辅助栈的栈顶时,将该元素压入辅助栈。当压入的值大于辅助栈的栈顶是,将辅助栈的栈顶复制一份,压入辅助栈。主弹出时,辅助栈也要同步弹出,辅助栈弹出的就是当前栈中的最小值。

面试题31 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列{1,2,3,4,5}是某栈的压栈序列,序列{4,5,3,2,1}是该压栈序列对应的一个弹出序列,但{4,3,5,1,2}就不可能是该压栈序列的弹出序列。

测试样例
  • 功能测试(是或者不是弹出序列)
  • 特殊值测试(两个序列位数不等;一个或者连个数列为空)
代码实现
public  boolean  isOutSeqOfStack(AnyType[] in,AnyType[] out) {
        if (in.length != out.length)
            return false;

        Stack<AnyType> assistStack = new Stack<>();
        int inIndex = 0, outIndex = 0;
        int length = in.length;
        while (in[inIndex].compareTo(out[outIndex]) == 0) {
            inIndex++;
            outIndex++;
            if (inIndex == length)
                return true;
        }
        assistStack.push(in[inIndex++]);
        assistStack.push(in[inIndex++]);

        while (true) {
            if (outIndex == length)
                return true;
            if (assistStack.isEmpty()){
                if (inIndex >length-1)
                    return false;
                else
                    assistStack.push(in[inIndex++]);
            }
            if (assistStack.peek().compareTo(out[outIndex]) == 0) {
                assistStack.pop();
                outIndex++;
            }else {
                assistStack.push(in[inIndex++]);
            }
        }
    }

    public static void main(String[] args) {
        Integer[] in={1,2,3};
        Integer[] out1={3,1,2};
        Integer[] out2={1,2,3};
        Integer[] out3={1,3,2};
        Integer[] out4={2,3,1};
        Integer[] out5={2,1,3};
        Integer[] out6={3,2,1};

        InterQuestions31<Integer> test = new InterQuestions31<>();

        System.out.println(test.isOutSeqOfStack(in,out1));
        //System.out.println(test.isOutSeqOfStack(in,out2));
    }
算法思路

建立一个辅助栈,一次进行判断。

面试题32 从上到下打印二叉树

不分行从上到下打印二叉树。丛上到下打印二叉树的么个节点,同一层的节点按照从左到右的顺序打印。二叉树节点的定义如下:

class BinaryTreeNode{
    int tValue;
    BinaryTreeNode left;
    BinaryTreeNode right;
}
测试用例
  • 功能测试(完全二叉树;所有节点只有左子树的二叉树;所有节点只有右子树的二叉树)
  • 特殊输入测试(二叉树根节点为null;只有一个节点的二叉树)
实现代码
  /**
     * 使用队列按层打印二叉树
     * @param tree 二叉树
     */
    public void printBinaryNodeTreeByLevel(BinaryTreeNode tree){
        Queue<BinaryTreeNode> queue = new LinkedBlockingDeque<>();
        if (tree == null)
            return;
        System.out.print(tree.nValue+" ");
        if (tree.left != null)
            queue.add(tree.left);
        if (tree.right != null)
            queue.add(tree.right);

        BinaryTreeNode head;
        while (queue.size()>0){
             head=queue.poll();
            System.out.print(head.nValue+" ");
            if (head.left != null)
                queue.add(head.left);
            if (head.right != null)
                queue.add(head.right);
        }
    }

    public static void main(String[] args) {
        BinaryTreeNode node1 = new BinaryTreeNode(8);
        BinaryTreeNode node2 = new BinaryTreeNode(6);
        BinaryTreeNode node3 = new BinaryTreeNode(10);
        BinaryTreeNode node4 = new BinaryTreeNode(5);
        BinaryTreeNode node5 = new BinaryTreeNode(7);
        BinaryTreeNode node6 = new BinaryTreeNode(9);
        BinaryTreeNode node7 = new BinaryTreeNode(11);

        node1.left=node2;
        node1.right = node3;
        node2.left=node4;
        node2.right=node5;
        node3.left = node6;
        node3.right = node7;

        /*//只有左子树
        node1.left = node2;
        node2.left = node4;*/

        InterQuestions32 test = new InterQuestions32();
        test.printBinaryNodeTreeByLevel(node1);
    }
算法思路

使用一个辅助队列。先将头节点推入队列中。每次从队列弹出一个节点打印,如果该节点有子节点,则按先左后右的顺序推入队列中。重复前面的操作,直至队列中所有的节点都被打印出来。

面试题33 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。例如,输入数组{5,7,6,9,11,10,8},则返回true,因为这个整数序列是一颗二叉搜索树的后序遍历结果。如果输入的数组是{7,4,6,5},则由于没有哪颗二叉搜索树的后序遍历结果是这个序列,因此返回false。

测试用例
  • 功能测试(完全二叉搜索树;所有节点都没有左/右子树的二叉树、只有一个节点的二叉树;输入的后续遍历序列没有对应的二叉搜索树)。
  • 特殊输入测试(输入的后续遍历序列为null)。
实现代码
/**
     * 验证序列是否为二叉搜索树的后续遍历
     * @param seq 要验证的序列
     * @param start  序列的起始位置
     * @param end 序列的结束位置
     * @return 是或否
     */
    public boolean verifySeqOfBST(int[] seq,int start,int end){
        //参数校验
        if (seq == null || seq.length == 0)
            return false;
        //成功出口,针对只有一个或两个节点的序列
        if (end == start || end-start == 1)
            return true;
        int root = seq[end];

        //找出右子树的第一个节点
        int i=start;
        for (;i<end;i++){
            if (seq[i]>root)
                break;
        }
        //验证右子树是否大于根节点
        int j=i;
        for (;j<end;j++){
            if (seq[j]<root)
                return false;
        }
        //左子序列递归验证
        boolean left = false;
        if (i>start)
            left = verifySeqOfBST(seq,start,i-1);
        //右子序列递归验证
        boolean right = false;
        if (i<end)
            right = verifySeqOfBST(seq,i,end-1);

        return left && right;
    }

    public static void main(String[] args) {
        InterQuestions33 test = new InterQuestions33();
        int[] a = {5,7,6,9,11,10,8};
        int[] b= {7,4,6,5};
        System.out.println(test.verifySeqOfBST(a,0,a.length-1));
        System.out.println(test.verifySeqOfBST(b,0,b.length-1));
    }
算法思想

在后序遍历的序列中,最后一个数字是树的根节点的值。数组中前面的数字可以分为两部分:第一部分是左子树节点的值,它们都比根节点小。第二部分是右子树节点的值,它们都比根节点的值大。

面试题34 二叉树中和为某一值的路径

输入一颗二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。二叉树定义如下:

class BinaryTreeNode{
    int tValue;
    BinaryTreeNode left;
    BinaryTreeNode right;
}
测试样例
  • 功能测试(树中包含0条、1条或多条符合的路径)
  • 特殊值测试(树为空;整数为空)
实现代码
/**
     * 求路径的核心方法
     * @param treeNode 二叉树
     * @param num 特定整数
     * @param sum 当前的和
     * @param stack 存放之前路径的栈
     */
    public void getSpecifiedPath(BinaryTreeNodeFor34 treeNode, int num, int sum, Stack<BinaryTreeNodeFor34> stack) {
        stack.push(treeNode);
        sum += treeNode.nValue;
        if (treeNode.left == null && treeNode.right == null && sum == num) {
            //使用foreach对栈进行遍历,方向是从栈底到栈顶
            for (BinaryTreeNodeFor34 aStack : stack)
                System.out.print(aStack.nValue + "-->");
            System.out.println();
        }
        if (treeNode.left != null)
            getSpecifiedPath(treeNode.left, num, sum , stack);
        if (treeNode.right != null)
            getSpecifiedPath(treeNode.right,num,sum,stack);

        stack.pop();
    }

    public static void main(String[] args) {
        BinaryTreeNodeFor34 node1 = new BinaryTreeNodeFor34(10);
        BinaryTreeNodeFor34 node2 = new BinaryTreeNodeFor34(5);
        BinaryTreeNodeFor34 node3 = new BinaryTreeNodeFor34(12);
        BinaryTreeNodeFor34 node4 = new BinaryTreeNodeFor34(4);
        BinaryTreeNodeFor34 node5 = new BinaryTreeNodeFor34(7);

        node1.left=node2;
        node1.right=node3;
        node2.left=node4;
        node2.right=node5;

        InterQuestions34 test = new InterQuestions34();
        test.getSpecifiedPath(node1,22,0,new Stack<>());
    }
    /* 测试结果
    10-->5-->7-->
    10-->12-->
     */
算法思路

二叉树的先序遍历正好是一条路径相连。使用栈存储已经遍历的路径,当遇到叶节点时,如果栈中所有元素和等于指定的整数,则打印该路径,然后返回上一层,继续遍历其他路径。

面试题35 复杂链表的复制

请实现一个函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个sibling指针指向链表中的任意节点或者null。节点的定义如下:

class ComplexListNode{
    int nValue;
    ComplexListNode nextNode;
    ComplexListNode siblingNode;
}
测试样例
  • 功能测试(节点中的sibling指向节点自身;两个节点的sibling形成环状;链表只有一个节点)。
  • 特殊输入测试(指向链表头节点的指针为null)
实现代码
/**
     * 主方法,负责方法调用和参数校验
     * @param head 复杂链表的头节点
     * @return 复制链表
     */
    public ComplexListNode clone(ComplexListNode head){
        if (head == null)
            return null;
        cloneNodes(head);// 复制原节点
        connectSiblings(head); //连接兄弟节点
        return reConnectNodes(head); //拆分并返回复制的复杂链表
    }

    /**
     * 克隆节点,并将节点连接到原节点之后
     * @param head 原链表的头节点
     */
    private void cloneNodes(ComplexListNode head){
        ComplexListNode current = head;
        while (current!=null){
            ComplexListNode cloneNode = new ComplexListNode();
            cloneNode.nValue = current.nValue;
            cloneNode.nextNode = current.nextNode;

            cloneNode.nextNode = current.nextNode;
            current.nextNode = cloneNode;

            current = cloneNode.nextNode;
        }
    }

    /**
     * 复制兄弟链
     * @param head 未复制兄弟链的链表
     */
    private void connectSiblings(ComplexListNode head) {
        ComplexListNode current = head;
        while (current != null){
            ComplexListNode cloneNodeOfCurrent = current.nextNode;
            //连接克隆节点的兄弟节点
            if (current.siblingNode !=null )
            cloneNodeOfCurrent.siblingNode = current.siblingNode.nextNode;
            current = cloneNodeOfCurrent.nextNode;
        }
    }

    /**
     * 拆分两个链表的方法
     * @param head 复制后,但连在一起的链表头节点
     * @return 复制的链表
     */
    private ComplexListNode reConnectNodes(ComplexListNode head) {
        ComplexListNode node = head;
        ComplexListNode cloneHead = null;
        ComplexListNode cloneNode = null;

        if (node != null){
            cloneHead=cloneNode=node.nextNode;
            node.nextNode=cloneNode.nextNode;
            node=node.nextNode;
        }

        while (node != null){
            cloneNode.nextNode=node.nextNode;
            cloneNode = cloneNode.nextNode;
            node.nextNode = cloneNode.nextNode;
            node = node.nextNode;
        }
        return cloneHead;
    }

    public static void main(String[] args) {
        InterQuestions35 test = new InterQuestions35();

        ComplexListNode node1 = new ComplexListNode();
        node1.nValue = 1;
        ComplexListNode node2 = new ComplexListNode();
        node2.nValue = 2;
        ComplexListNode node3 = new ComplexListNode();
        node3.nValue = 3;
        ComplexListNode node4 = new ComplexListNode();
        node4.nValue = 4;
        ComplexListNode node5 = new ComplexListNode();
        node5.nValue = 5;

        node1.nextNode = node2;
        node1.siblingNode = node3;
        node2.nextNode = node3;
        node2.siblingNode = node5;
        node3.nextNode = node4;
        node3.siblingNode = null;
        node4.nextNode = node5;
        node4.siblingNode = node2;

        ComplexListNode clone = test.clone(node1);
        while (clone!=null){
            System.out.print("node:"+clone.nValue);
            if (clone.nextNode!=null)
                System.out.print("next:"+clone.nextNode.nValue);
            if (clone.siblingNode!=null)
                System.out.print(":sibling"+clone.siblingNode.nValue);
            clone = clone.nextNode;
            System.out.println();
        }
    }
    /* 测试结果
    node:1next:2:sibling3
    node:2next:3:sibling5
    node:3next:4
    node:4next:5:sibling2
    node:5
     */
算法思路

总共可以分为3步。第一步,复制每一个节点,并将复制节点连接到该节点后;第二部,复制每一个节点的兄弟链siblingNode;第三步,拆分为两个链表,并返回复制的链表。

面试题36 二叉搜索树与双向链表

输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。二叉树的节点定义如下:

class BinaryTreeNode{
    int tValue;
    BinaryTreeNode left;
    BinaryTreeNode right;
}
测试样例
  • 功能测试(二叉树有多个节点;二叉树只有一个节点;二叉树所有节点只有左子树或右子树)
  • 特殊测试(树的根节点为null)
实现代码
 /**
     * 主方法,负责递归方法ConvertNode的调用
     * @param rootOfTree 二叉搜索树的根
     * @return 双向链表的最小值节点
     */
    public BinaryTreeNode convert(BinaryTreeNode rootOfTree){
        BinaryTreeNode lastNodeInList = null;
        lastNodeInList= ConvertNode(rootOfTree, null);

        BinaryTreeNode headOfList = lastNodeInList;
        while (headOfList != null && headOfList.left != null)
            headOfList = headOfList.left;

        return headOfList;

    }

    /**
     * 转换的核心方法,递归地改变链的指向
     * @param node 当前树的根节点
     * @param lastNodeInList 左链的最小节点
     * @return 当前链的最大节点
     */
    private BinaryTreeNode ConvertNode(BinaryTreeNode node, BinaryTreeNode lastNodeInList) {
        if (node == null)
        return null;

        if (node.left != null){
            lastNodeInList=ConvertNode(node.left,lastNodeInList);
        }

        node.left = lastNodeInList;
        if (lastNodeInList!=null)
            lastNodeInList.right = node;
        lastNodeInList = node;

        if (node.right != null)
            lastNodeInList = ConvertNode(node.right,lastNodeInList);
        return lastNodeInList;
    }

    public static void main(String[] args) {
        BinaryTreeNode node1 = new BinaryTreeNode(10);
        BinaryTreeNode node2 = new BinaryTreeNode(5);
        BinaryTreeNode node3 = new BinaryTreeNode(12);
        BinaryTreeNode node4 = new BinaryTreeNode(4);
        BinaryTreeNode node5 = new BinaryTreeNode(7);

        node1.left=node2;
        node1.right=node3;
        node2.left=node4;
        node2.right=node5;

        InterQuestions36 test =new InterQuestions36();
        BinaryTreeNode list= test.convert(node1);
        while (list!=null){
            System.out.print(list.nValue +",");
            list = list.right;
        }
    }
    /*测试结果
    4,5,7,10,12,
     */
算法思路

把二叉搜索树看成3部分,根节点,左子树,右子树。先把左子树完成转换,左子树的最后一个节点是链表的最大值,将其与根节点相连,并把右子树的最小值与根相连。递归进行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值