【剑指Offer】解决面试题的思路(上)

二叉树的镜像

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。

分析

下图中右边的树就是左边的树的镜像。
在这里插入图片描述
镜像树和原树之间的关系是:根节点一样,左右节点交换位置。
在这里插入图片描述
如上图所示,第一次交换之后,其实是6和1左右两棵子树之间交换了位置。由此类推,只要一个结点存在孩子结点,就交换两个子节点或是子树(有一个为空也可以)。上图中第二次实现了结点1的左右子结点的交换,第三次交换实现了结点6的左右子结点交换。最终就得到了镜像树。

Java代码

public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null)  //根节点为空
            return;
        if(root.left == null && root.right == null) //左右两个子结点都为空
            return;
        
        // 交换左右结点
        TreeNode p = root.left;
        root.left = root.right;
        root.right = p;
        
        if(root.left != null) //左结点若不为空,则同理处理左子树
            Mirror(root.left);
        if(root.right != null) //右结点若不为空,则同理处理右子树
            Mirror(root.right);
    }
}

顺时针打印矩阵

题目描述

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

分析

顺时针打印矩阵,可以通过画图的方式开分析问题。假设矩阵的行数为lines,列数为columns。设左上角的元素坐标为 ( 0 , 0 ) (0,0) (0,0)
最一般的情况如图(a)所以,顺时针打印一圈就是先从左往右,再从上往下,再从右往左,最后从下往上打印一圈,一共是四步。

可以总结其中的规律:在打印第 i i i圈的时候 ( i = 0 , 1 , 2 , . . . ) (i=0,1,2,...) (i=0,1,2,...)
即打印第i个循环时,令start = i,endX=lines-1-start,endY=columns-1-start则:

stepxy
1startstart → endY
2(start+1) → endXendY
3endX(endY-1) → start
4endX → (start-1)start

下面考虑终止条件,即循环打印的次数count。
c o u n t = [ m i n ( l i n e , c o l u m n ) + 1 ] / 2 count = [\mathrm{min}(line, column)+1] / 2 count=[min(line,column)+1]/2

在这里插入图片描述
此外,还要考虑打印最后一圈时的情况。
在这里插入图片描述
打印最后一圈不可能为4步,只可能小于4步。图(b)、( c )、(d)分别展示了最后一个循环打印3步、2步、1步的情况。可见,只要没有打印结束,总要打印第1步。

  • 是否打印第2步则要看是否满足endX>startX,若满足,则执行;
  • 是否打印第3步则要看是否满足endX>startX且endY>startY,若满足,则执行;
  • 是否答应第4步则要看是否满足endY>startY且endX-startX>=2,若满足,则执行。
    其中,startX=startY=start

Java代码

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        int lines = matrix.length;
        int columns = matrix[0].length;
        int count = (Math.min(lines, columns)-1) / 2;
        
        for(int i=0; i<count; i++){
            int start = i;
            int endX = lines - 1 - start;
            int endY = columns - 1 - start;
            // 从左向右
            for(int k=start; k<=endY; k++)
                list.add(matrix[start][k]);
            // 从上向下
            if(endX > start)
                for(int k=start+1; k<=endX; k++)
                    list.add(matrix[k][endY]);
            // 从右向左
            if(endX>start && endY>start)
                for(int k=endY-1; k>=start; k--)
                    list.add(matrix[endX][k]);
            // 从下向上
            if(endY>start && endX-start>1)
                for(int k=endX-1; k>start; k--)
                    list.add(matrix[k][start]);
        }
        return list;
    }
}

复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点或者null),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。
链表结点:

public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}

分析

如下图是一个由五个结点复杂链表。
在这里插入图片描述
第一步:复制每一个结点,如下图所示:
在这里插入图片描述
第二步:复制random指针
在这里插入图片描述
第三步:拆分链表
在这里插入图片描述

Java代码

public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null)
            return null;
        CloneNode(pHead);
        CloneRandomPointer(pHead);
        return(SplitLink(pHead));
    }
    // 第一步
    public void CloneNode(RandomListNode pHead){
        RandomListNode pCurrent = pHead;
        while(pCurrent != null){
            RandomListNode cloneNode = new RandomListNode(pCurrent.label);
            cloneNode.next = pCurrent.next;
            pCurrent.next = cloneNode;
            pCurrent = cloneNode.next;
        }
    }
    // 第二步
    public void CloneRandomPointer(RandomListNode pHead){
        RandomListNode pCurrent = pHead;
        while(pCurrent != null){
            if(pCurrent.random != null)
                pCurrent.next.random = pCurrent.random.next;
            else
                pCurrent.next.random = null;
            pCurrent = pCurrent.next.next;
        }
    }
    // 第三步
    public RandomListNode SplitLink(RandomListNode pHead){
        RandomListNode pCurrent = pHead;
        RandomListNode pHeadClone = pHead.next;
        while(pCurrent != null){
            RandomListNode pCurClone = pCurrent.next;
            pCurrent.next = pCurClone.next;
            pCurClone.next = (pCurClone.next==null) ? null : pCurClone.next.next;
            pCurrent = pCurrent.next;
        }
        return pHeadClone;
    }
}

二叉搜索树和双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。结果返回头结点。
在这里插入图片描述
结点的定义为:

public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}

分析

已知,搜索二叉树的特点是:左子结点的值总是小于父结点的值,右子结点的值总是大于父结点的值。转换以后的双向链表从左到右的顺序与中序遍历一样,所以使用中序遍历的方法来处理。
在这里插入图片描述
其关键就是:
curLast.right = pRootOfTree;
pRootOfTree.left = curLast;
curLast = pRootOfTree;

Java代码

public class Solution {
    TreeNode curLast = null; //指示当前处理的结点,即以生成的双向链表的最后一个结点
    TreeNode listHead = null; // 标志双向链表的头结点
    public TreeNode Convert(TreeNode pRootOfTree) {
        ConvertSub(pRootOfTree);
        return listHead;
    }
    public void ConvertSub(TreeNode pRootOfTree){
        if(pRootOfTree == null)
            return;
        ConvertSub(pRootOfTree.left);
        if(curLast == null){
            curLast = pRootOfTree;
            listHead = pRootOfTree;
        }
        else{
            curLast.right = pRootOfTree;
            pRootOfTree.left = curLast;
            curLast = pRootOfTree;
        }
        ConvertSub(pRootOfTree.right);
    }
}

字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

分析

利用递归的思想来解决。
首先将一个字符串看成两个部分,

  • 第①部分:第一个字符;
  • 第②部分:除第一个字符外的所有字符;

将第一个字符与第二部分中的所有字符交换,如下图所示:
在这里插入图片描述
交换之后的字符串用同样的方法处理。
如题中的例子"abc",整个过程如下图所示:
在这里插入图片描述
最后一行就是排列出来的所有可能的结果。

此外还要考虑重复的清空,所以将结果添加到List中去的时候,还要检查是否重复。先使用TreeSet存放元素会减少查重的工作。

Java代码

import java.util.*;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<String>();
        //若字符串不存在,或字符串为空
        if(str==null || str.length()==0)
            return res;
        //字符串不为空
        char[] array = str.toCharArray(); //字符串中的元素处理不方便,故转换成数组
        TreeSet<String> set = new TreeSet<String>(); // 使用TreeSet可以避免重复
        Permutation(array, 0, set);
        res.addAll(set); // 将TreeSet中的所有结果添加到res中
        return res;
    }
    public void Permutation(char[] array, int begin, TreeSet<String> set){
        // 若字符串不存在或为空,或者begin元素错误
        if(array==null || array.length==0 || begin<0 || begin>array.length-1)
            return;
        // 递归结束标志,begin指向数组的最后一个元素
        if(begin == array.length-1)
            set.add(String.valueOf(array)); // 将该结果添加到TreeSet中
        // 递归未结束
        else{
            // 将第二部分中的每一个元素和第一个元素进行交换,对新字符串进行递归处理
            for(int i=begin; i<array.length; i++){
                swap(array, begin, i);
                Permutation(array, begin+1, set);
                swap(array, begin, i);//这一步不可省略!!!交换结束后还要交换回来进行下一次处理。
            }
        }
    }
    public void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值