剑指offer刷题详细分析:part4:16题——20题

  • 剑指offer所有题目详解,可访问我的github项目:KongJetLin-offer

  • 目录

  1. Number16:合并2个排序的链表
  2. Number17:树的子结构
  3. Number18:二叉树的镜像
  4. Number19:顺时针打印矩阵
  5. Number20:包含min函数的栈

题目16 合并2个排序的链表

  题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
  可以使用递归法或者排序法来完成,代码如下:

递归法
//1、递归法
    //这个方法的含义是,构建以结点 list1与list2 中值较小的为头结点的链表,
    // 并将list1链表以及list2链表剩下的结点按从小到大的顺序连接在该头结点后面,返回该链表
    public ListNode Merge(ListNode list1,ListNode list2)
    {
        //首先,如果list1已经为null,返回list2;如果list2也为null,返回list1(null),当然此处也可以返回null
        if(list1 == null)
            return list2;
        if(list2 == null)
            return list1;

        //找出list1与list2中值较小那个,将其作为新链表头结点(假设是list1),那么继续寻找 list1.next 与 list2中较小的结点,接到list1后面
        //最后返回list1,以list1为头结点的链表就会接到上一层较小链表的后面(自己画个图就明白)
        if(list1.val < list2.val)
        {
            list1.next = Merge(list1.next , list2);//寻找 list1.next 与 list2中较小的结点,接到list1后面
            return list1;//返回list1,以list1为头结点的链表就会接到上一层较小链表的后面
        }
        else
        {
            list2.next = Merge(list1 , list2.next);
            return list2;
        }
    }
迭代法
//2、迭代法
    public ListNode Merge1(ListNode list1,ListNode list2)
    {
        //首先,构造一个虚拟头结点,这个头结点用于连接最后要返回的链表,值为-1
        ListNode head = new ListNode(-1);
        //其次,我们必须要创建一个指针指向虚拟头结点,这个指针向下移动寻找list1与list2中较小的一个并将其连接到cur上,
        //由于cur与head原来指向同一个结点,那么连接到cur.next的结点,同样是连接到以head为头结点的链表上(画个图就明白)
        //本来使用head就够了,但是最后要返回链表头结点,因此只能再创建一个cur指针来移动。
        ListNode cur = head;

        //当list1与list2 不全为null的时候,我们寻找其中较小的一个,连接到cur上,并将cur后移一位
        while(list1 != null && list2 != null)
        {
            if(list1.val < list2.val)
            {
                cur.next = list1;
//                cur = cur.next;
                list1 = list1.next;//同时,由于list1已经添加到cur链表,我们将其后移一位继续添加
            }
            else
            {
                cur.next = list2;
//                cur = cur.next;//这句可以直接放到外面
                list2 = list2.next;
            }
            cur = cur.next;
        }

        //当list1与list2中一个为null,跳出循环
        if(list1 != null)//如果是list2为null跳出循环,list1不为null,将list1剩下的结点接到cur后面
            cur.next = list1;
        if(list2 != null)//如果是list1为null跳出循环,list2不为null,将list2剩下的结点接到cur后面
            cur.next = list2;
        //不会出现list1与list2都为null的情况

        return head.next;//通过cur指针将结点连接到以head为头结点的链表上,返回head.next为头结点的链表即可!
    }

题目17 树的子结构

  题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

  分析
  第一步在树A中查找与B根节点一样的节点,实际上就是树的遍历,可以采用递归的方式,即对比A当前结点与B根结点是否相同,不同就递归判断 A的左右孩子结点与B的根结点是否相同。
  第二步,如果找到A中某个结点node与B的根结点值相同,则递归地判断它们各自的左右孩子节点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶节点。
  代码如下:

//这个方法用于判断 树root2是不是树root1的子结构(子树)
    public boolean HasSubtree(TreeNode root1,TreeNode root2)
    {
        /*
        进方法的时候,有几种情况:
        1)root1=null,root2=null/root1!=null,root2=null,由于约定空树不是任意一个树的子结构
            即root2进来就是null,那么直接返回false;
        2)root1=null,root2!=null,说明root1递归到叶子结点,但是root2还有下面的结点,同样不满足,返回false

        即只有 root1,root2均不为null的时候,才需要继续判断;揉揉腿1或者root2有一个是null,终止判断返回false
         */
        if(root1 == null || root2 == null)
            return false;


        //定义变量flag,用于保存最后的结果
        boolean flag = false;

        //对比A当前结点与B根结点是否相同,相同调用 isSubTree() 方法递归地判断它们各自的左右孩子节点的值是不是相同
        if(root1.val == root2.val)
        {
            //判断 以root2为根的树 是不是 以root1为根的树 的子树,保存判断结果
            flag = isSubTree(root1 , root2);
        }
        //如果当前 以root2为根的树 不是 以root1为根的树 的子树,flag=false,
        // 递归判断 树root2是不是 树root1.left 或者 树root1.right 的子结构,保存递归结果。(此处 树root1 表示以root1为根的子树)
        if(!flag)
        {
            //只要有一个子结构满足即可,因此用“或”
            flag = HasSubtree(root1.left , root2) || HasSubtree(root1.right , root2);
        }

        return flag;
    }

    //这个方法用于判断根值相同的2个树node1,node2,node2是不是node1的子树
    private boolean isSubTree(TreeNode node1 , TreeNode node2)
    {
        /*
        遍历到最后有几种情况:
        1)node1!=null,node2=null,说明node2递归到底端,但是node1没有到底端,前面父结点满足,返回true;
        2)node1=null,node2=null,说明node1,node2递归到底端,前面父结点满足,返回true;
        3)node1=null,node2!=null,说明说明node1递归到底端,但是node2还没,那么不满足,返回false
        4)node1!=null,node2!=null,继续判断。

        即,只要node2=null,返回true;node1=null,node2!=null,返回false,否则继续执行
         */
        if(node2 == null)
            return true;
        if(node1==null && node2!=null)
            return false;

        //如果node1,node2值相同,继续递归判断他们对应的子结点是否相同
        if(node1.val == node2.val)
        {
            //左右子树必须全部相同才可以返回true,用“与”
            return isSubTree(node1.left , node2.left) && isSubTree(node1.right , node2.right);
        }
        else
        {
            //当有一个结点值不同,说明当前子结构不满足,返回false
            return false;
        }
    }

题目18 二叉树的镜像

  题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。

  分析:我们从二叉树的根结点开始,把当前结点的左右孩子结点交换,这样当前结点的左右子树就完成镜像,但是左右子树的子树还没有完成镜像,因此我们接下来通过递归的方式,将左右孩子结点的左右子树进行交换…直到当前结点为null,结束递归!

  代码如下:

public void Mirror(TreeNode root)
    {
        //如果当前结点为null,那么当前结点就没有左右孩子结点,不需要镜像
        if(root == null )
            return;
        else
        {//当当前结点不为null,如果当前结点的左右孩子结点都为null,也不需要镜像(但是其实镜像也没有关系,如果判断,最优的情况下可以减少接近一半的镜像!)
            if((root.left==null && root.right==null))
                return;
        }
        //先将当前结点的左右子树交换
        swap(root);
        //下面交换左右孩子结点的左右子树
        Mirror(root.left);
        Mirror(root.right);
    }

    private void swap(TreeNode node)
    {
        TreeNode temp = node.right;
        node.right = node.left;
        node.left = temp;
    }

题目19 顺时针打印矩阵

  题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

  分析:从外圈到内圈的顺序依次打印的,每次打印一个圈的数字。
  下来分析如何打印一圈,将打印一圈分为4步:
1)从左到右打印一行
2)从上到下打印一列
3)从右到左打印一行
4)从下到上打印一列
  每一步根据起始坐标和终止坐标来用一个循环即能实现。
在这里插入图片描述
  分析打印时每一步的前提条件:
  第一步是必须的,因为打印一圈至少有一步;如果只有一行,那么就不需要第二步了,也就是说第二步的前提条件是终止行号大于起始行号;需要第三步的前提条件是圈内至少有两行两列,也就是说除了终止行号大于起始行号,还要求终止列号大于起始列号;第四步的前提条件是至少有三行两列,因此要求终止行号比起始行号至少大2,同时终止列号大于起始列号

  代码如下:

public static ArrayList<Integer> printMatrix(int [][] matrix)
    {
        ArrayList<Integer> arrayList = new ArrayList<>();

        //在行、列分别定义2个指针,指向行列的首尾
        int r1 = 0 , r2 = matrix.length-1;
        int c1 = 0 , c2 = matrix[0].length-1;

        //当 r1>r2 且 c1>c2的时候,这时候所有的圈都遍历完了,结束遍历(这种情况下,最后一圈只有一个数字,即r1=r2 且 c1=c2的情况也考虑到)
        while(r1<=r2 && c1<=c2)
        {
            //从左到右是必须的
            for (int i = c1; i <= c2 ; i++)
            {
                arrayList.add(matrix[r1][i]);
            }

            //当终止行号大于起始行号,从上到下
            if(r2 > r1)
            {
                for (int i = r1+1; i <= r2 ; i++)
                {
                    arrayList.add(matrix[i][c2]);
                }
            }

            //除了终止行号大于起始行号,还要求终止列号大于起始列号,从右到左
            if(r2 > r1 && c2 > c1)
            {
                for (int i = c2-1; i >= c1 ; i--)
                {
                    arrayList.add(matrix[r2][i]);
                }
            }

            //终止行号比起始行号至少大2,同时终止列号大于起始列号
            if(c2 > c1 && r2-2 >= r1)
            {
                for (int i = r2-1; i > r1 ; i--)
                {
                    arrayList.add(matrix[i][c1]);
                }
            }
            //最后,遍历完一圈记得调整行列首尾指针
            r1++; r2--; c1++; c2--;
        }

        return arrayList;
    }


题目20 包含min函数的栈

  题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
  注意:
1)保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法;
2)min方法的时间复杂度应该为O(1)。
  如果只使用一个数据结构实现,需要每次压入一个新元素进栈时,将栈里的所有元素排序,让最小的元素位于栈顶。但是这种想法不能保证最后压入栈的元素能够最先出栈,因为这个数据结构已经不是栈了。
  我们创建2个Stack,一个dataStack正常入栈出栈元素,另一个minStack保存最小的元素。每次压入一个新元素进栈的时候,如果该元素比当前最小的元素还要小,则minStack更新最小元素,dataStack也添加一个元素;由于每次dataStack出栈的时候,为了保证元素的顺序性,minStack也要出栈栈顶元素,否则单单是dataStack出栈而minStack不出栈,2个栈元素的顺序性不同。
  因此,对于minStack,它的栈的元素个数应该与dataStack保持相同。如果先插入的元素小于栈顶元素(栈顶元素最小),我们将元素入栈到栈顶,如果新插入的元素大于等于栈顶元素,我们就再次存入栈顶元素。 这样,我们每次取min的时候,都能保证取到当前序列的最小元素,因为此时取到栈顶元素,而minStack栈其他元素都小于或者等于栈顶元素,必然可以保证取到的元素最小!
  代码如下:

private Stack<Integer> dataStack = new Stack<>();
    private Stack<Integer> minStack = new Stack<>();
    public void push(int node)
    {
        //入栈的时候,2个栈都入栈
        dataStack.push(node);
        //minStack为空,直接入栈node,否则入栈node与栈顶元素较小的一个。
        minStack.push( minStack.isEmpty() ? node : Math.min(node , minStack.peek()));
    }

    public void pop()
    {
        if(dataStack.size() > 0 && minStack.size() > 0)
        {
            //当2个栈不为空,才可以出栈元素(其实2个栈元素个数一样,判断一个即可)
            dataStack.pop();
            minStack.pop();
        }
    }

    public int top()
    {
        return dataStack.peek();//直接读取dataStack栈顶元素即可(不会讲dataStack栈顶元素弹出,只是读取)
    }

    public int min()
    {
        return minStack.peek();//直接读取minStack栈顶元素即可(不会讲mintack栈顶元素弹出,只是读取)
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值