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

面试题19 正则表达式匹配

请实现一个函数用来匹配包含’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串“aaa”与模式“a.a”和“ab*ac*a”匹配,但与“aa.a”和“ab*a”均不匹配。

测试样例
  • 功能测试(模式字符串里包含普通字符、’.’、’*’;模式字符串和输入字符串匹配/不匹配)
  • 特殊输入测试(输入字符串和模式字符串是null、空字符串)
实现代码
/**
     * 字符串匹配的主方法,负责处理字符串为空等特殊情况,以及核心递归方法的调用
     * @param str 字符串
     * @param pattern 模式
     * @return true or false
     */
    public static boolean match(String str,String pattern){
        if (str == null || pattern == null)
            return false;
        if (str.equals("") && pattern.equals(""))
            return true;
        return matchCore(str,0,pattern,0);
    }

    /**
     * 匹配的核心递归方法
     * @param str 字符串
     * @param strIndex 当前遍历到的字符串的位置
     * @param pattern 模式
     * @param ptnIndex 当前遍历到的模式的位置
     * @return true or false
     */
    private static boolean matchCore(String str,int strIndex, String pattern,int ptnIndex) {
        //出口1,遍历完
        if (sIndex == s.length() && ptnIndex == p.length())
            return true;
        //出口2,s遍历完了,p没完,继续判断p的第二位是不是*
        if (sIndex == s.length() && ptnIndex != p.length()){
            if (ptnIndex+1<p.length() && p.charAt(ptnIndex+1)=='*')
                return matchCore(s,sIndex,p,ptnIndex+2);
            else
                return false;
        }

        //出口3,s没完,p完了
        if (sIndex != s.length() && ptnIndex == p.length())
            return false;

        //第二个字符是*
        if (ptnIndex+1<p.length() && p.charAt(ptnIndex+1) == '*'){
            //第一个字符匹配
            if (p.charAt(ptnIndex) == '.' || s.charAt(sIndex)==p.charAt(ptnIndex)){
                return matchCore(s,sIndex+1,p,ptnIndex+2) //匹配一次
                      || matchCore(s,sIndex+1,p,ptnIndex) //匹配多次
                      || matchCore(s,sIndex,p,ptnIndex+2);  //匹配0次
            }
            else
                return matchCore(s,sIndex,p,ptnIndex+2);
        }
        //第二个字符不是*
        if (p.charAt(ptnIndex) == '.' || s.charAt(sIndex) == p.charAt(ptnIndex))
            return matchCore(s,sIndex+1,p,ptnIndex+1);
        return false;
    }

    public static void main(String[] args) {
        String str = "a";
        String pattern = ".";
        System.out.println(str+" "+match(str,pattern)+" "+pattern);

        String str1 = "aaa";
        String pattern1 = "ab*ac*a";
        System.out.println(str1+" "+match(str1,pattern1)+" "+pattern1);

        String str2 = "aaa";
        String pattern2 = "aa.a";
        System.out.println(str2+" "+match(str2,pattern2)+" "+pattern2);
        String str3 = "aaa";
        String pattern3 = "ab*a";
        System.out.println(str3+" "+match(str3,pattern3)+" "+pattern3);
    }
算法思路

这道题的核心其实在于分析’*’,对于’.’来说,它和任意字符都匹配,可把其当做普通字符。对于’*’的分析,我们要进行分情况讨论,当所有的情况都搞清楚了以后,就可以写代码了。

  • 在每轮匹配中,Patttern第二个字符是’*’时
    • 当第一个字符不匹配时,那么代表模式的一个字符出现0次,模式略过前两个字符,字符串不变。如“ba”和“a*ba”。
    • 当第一个字符匹配时,可能匹配0次,1次、多次。比如“aaa”与“a*aaa”,“abc”与“a*bc”,“aaaba”与“a*ba”。匹配0次时,字符串不变,模式向后跳2位,然后匹配剩余字符串和模式;匹配1次时,字符串向后移动1位,模式向后移动两位;匹配多次时,字符串向后移动一位,模式不变;
  • 而当Patttern第二个字符不是’*’时,情况就简单多了:
    • 如果字符串的第一个字符和模式中的第一个字符匹配,那么在字符串和模式上都向后移动一个字符,然后匹配剩余字符串和模式。
    • 如果字符串的第一个字符和模式中的第一个字符不匹配,那么直接返回false。

面试题20 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串“+100”、“5e2”、“-123”、“3.1416”及“-1E-16”都表示数值,但“12e”、“1a3.14”、“1.2.3”、“+-5”及“12e+5.4”都不是。

测试用例
  • 功能测试(正数或者负数;包含或者不包含整数部分的数值;包含或者不包含小数部分的数值;包含或者不包含指数部分的数值;各种不能表达有效数值的字符串)。
  • 特殊输入测试(输入字符串和模式字符串是null、空字符串)
实现代码
/**
     * 字符串的字符数组形式
     * @param str 字符数组
     * @return true or false
     */
    public boolean isNumeric(char[] str) {
        //标记符号、小数点、e是否出现过
        boolean sign = false;
        boolean decimal = false;
        boolean hasE = false;
        for (int i=0;i<str.length;i++) {
            if (str[i] == 'e' || str[i] == 'E') {
                if (i == str.length - 1) return false;
                if (hasE) return false;
                hasE = true;
            } else if (str[i] == '+' || str[i] == '-') {
                if (sign && str[i - 1] != 'e' && str[i - 1] != 'E') return false;
                if (!sign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false;
                sign = true;
            } else if (str[i] == '.') {
                if(hasE||decimal) return false;
                decimal = true;
            } else if (str[i] < '0' || str[i] > '9') {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        InterQuestions20 test = new InterQuestions20();
        System.out.println(test.isNumeric("-1E-16".toCharArray()));
        System.out.println(test.isNumeric("12e+4.3".toCharArray()));
    }
算法思路

表示数值的字符串遵循模式A[.[B]][e|EC]或者.B[e|EC]。其中A表示整数部分,可以带符号。B是小数部分,C紧跟着’e’或’E’,是数值的指数部分。在小数里可能没有整数部分,如“.234”。上面A、B和C都是0~9的数位串,但是B前面不能有正负号。按照此模式来完成。

面试题21 调整数组顺序使技术位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

测试用例
  • 功能测试(输入一个数组,全为奇数;全为偶数;奇数偶数均有;)
  • 特殊值测试(数组为null,数组为空,数组只有一个元素)
实现代码
/**
     * 根据isOdd和isEven的标准将数组元素分为前后两个部分
     * @param array 数组
     */
    public static void reorderOddEven(int[] array){
        if (array == null || array.length<1)
            return;

        int odd = 0,even = array.length-1;
        int temp=0;
        while (odd<even) {
            temp=0;
            if (isOdd(array[odd])) {
                odd++;
                continue;
            }
            if (isEven(array[even])) {
                even--;
                continue;
            }

            temp = array[even];
            array[even] = array[odd];
            array[odd] = temp;
        }
    }

    /**
     * 数组前端的判断标准
     * @param odd 要判断的数
     * @return true or false
     */
    private static boolean isOdd(int odd){
        return odd % 2 != 0;
    }

    /**
     * 数组后端的判断标准
     * @param even 数
     * @return true or false
     */
    private static boolean isEven(int even){
        return even % 2 == 0;
    }

    public static void main(String[] args) {
        int[] arr = {1,5,6,4,3,2};
        reorderOddEven(arr);
        for (int i : arr) {
            System.out.print(i+",");
        }
    }
算法思路

设置头尾两个指针,分别从前往后遍历,前后都遇到符合条件的数字就交换。类似于快速排序的思想。同时可以将判断标准独立出来,形成可扩展的方法。

面试题22 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第一个节点。例如,一个链表有6个节点,从头节点开始,它们的值一次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。链表节点定义如下:

/**
 * 链表节点类
 */
class ListNode{
    int nodeValue;
    ListNode nextNode;
}
测试样例
  • 功能测试(第k个节点在链表中间;第k个节点是链表的头节点;第k个节点是链表的尾节点)
  • 特殊输入测试(链表头节点为null,链表的节点总数少于k;k等于0)
实现代码
/**
 * 链表节点类
 */
class ListNode{
    int nodeValue;
    ListNode nextNode;

    public ListNode(int nodeValue) {
        this.nodeValue = nodeValue;
    }
}

public static ListNode lastButNth(ListNode head,int k){
        if (head == null || k==0)
            return null;
        ListNode one=head,two = head;
        for (int i=0;i<k-1;i++)
            if (two.nextNode != null)
            two = two.nextNode;
        else
            return null;

        while (two.nextNode != null){
            two = two.nextNode;
            one = one.nextNode;
        }
        return one;
    }

    public static void main(String[] args) {
        ListNode root = new ListNode(1);
        ListNode node1 = new ListNode(2);
        ListNode node2 = new ListNode(3);
        ListNode node3 = new ListNode(4);
        ListNode node4 = new ListNode(5);
        ListNode node5 = new ListNode(6);

        root.nextNode = node1;
        node1.nextNode = node2;
        node2.nextNode = node3;
        node3.nextNode = node4;
        node4.nextNode = node5;

        System.out.println(lastButNth(node5,3).nodeValue);
    }
算法思路

定义两个指针。第一个指针从链表头部走k-1步,第二个指针保持不动;从第k步开始,第二个指针也开始从链表头部开始遍历。当第一个指针到达尾部时,第二个指针所在位置就是倒数第k个元素。

面试题23 链表中环的入口节点

如果一个链表中包含环,如何找出环的入口节点?

测试样例
  • 功能测试(链表包含环;不包含环)
  • 特殊值测试(链表为空;链表头指针为null)
实现代码
    public static ListNode EntryNodeOfLoop(ListNode head){
        ListNode mettingNode = mettingNode(head);
        if (mettingNode == null)
            return null;

        int i = 1;
        ListNode next = mettingNode.nextNode;
        while (mettingNode != next){
            next = next.nextNode;
            i++;
        }

        ListNode one = head,two = head;
        for (int j=0;j<i;j++ ){
            one = one.nextNode;
        }
        while (one != two){
            one = one.nextNode;
            two = two.nextNode;
        }
        return one;
    }

    private static ListNode mettingNode(ListNode head) {
        ListNode slow = head.nextNode;
        if (slow == null)
            return null;
        ListNode fast = slow.nextNode;
        while (fast != null){
           if (slow == fast)
               return fast;
           slow = slow.nextNode;
           fast = fast.nextNode;
           if (fast != null)
               fast = fast.nextNode;
        }
        return null;
    }


    public static void main(String[] args) {
        ListNode root = new ListNode(1);
        ListNode node1 = new ListNode(2);
        ListNode node2 = new ListNode(3);
        ListNode node3 = new ListNode(4);
        ListNode node4 = new ListNode(5);
        ListNode node5 = new ListNode(6);

        root.nextNode = node1;
        node1.nextNode = node2;
        node2.nextNode = node3;
        node3.nextNode = node4;
        node4.nextNode = node5;
        node5.nextNode = node3;

        System.out.println(mettingNode(root).nodeValue);
        System.out.println(EntryNodeOfLoop(root).nodeValue);
    }
算法思路

如何判断链表有环?
使用一快一慢两个指针,指针1一次走1步,指针2一次走两步。如果两个指针相遇,那么链表中存在环。
如何找到环的入口?
设环有n个节点。使用两个指针,一个先走n步,当两个节点相遇时,相遇的地点就是环的入口。
如何计算出环有几个节点?
在判断链表是否有环的时候,两个指针相遇的节点一定属于环内。让该指针继续移动,当指针再次回到该点时,经过的节点个数就是换的节点个数。

面试题24 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。链表节点定义同面试题23.

测试用例
  • 功能测试(链表有多个节点,链表只有一个节点)
  • 特殊值测试(输入的头节点等于null)
实现代码
/**
     * 逆序的接口方法
     * @param head 链表头节点
     * @return 逆序后的头节点
     */
    public static ListNode reverseLinkList(ListNode head){
        if (head == null)
            return null;
        ListNode pre = null,cur = head,next = cur.nextNode;
        if (next == null)
            return head;
        return reverseCore(pre,cur,next);
    }

    /**
     * 逆序的核心递归方法
     * @param pre 当前已经逆序的最后一个节点
     * @param cur 当前没有逆序的第一个节点
     * @param next 当前没有逆序的第二个节点
     * @return 已经逆序的链表的头节点
     */
    public static ListNode reverseCore(ListNode pre,ListNode cur,ListNode next){
        cur.nextNode = pre;
        if (next == null)
            return cur;
        return reverseCore(cur,next,next.nextNode);
    }
算法思路

定义三个节点pre,cur,next,分别表示当前已经逆序的最后一个节点、当前没有逆序的第一个节点、当前没有逆序的第二个节点。cur的next指针指向pre,就完成了cur节点的逆序。cur变为pre,next变为cur,递归进行。知道next等于null,则将最后一个节点逆序并返回。

面试题25 合并两个排序的链表

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

测试样例
  • 功能测试(输入的两个链表有多个节点;节点的值互不相同或者存在值相等的多个节点)
  • 特殊输入测试(两个链表为null或者二者之一为null;两个链表中只有一个节点。
实现代码
/**
     * 递归地合并两个链表
     * @param list1 链表1
     * @param list2 链表2
     * @return 合并后的链表
     */
    public static ListNode mergeList(ListNode list1,ListNode list2) {
        if (list1 == null)
            return list2;
        if (list2 == null)
            return list1;

        ListNode mergeHead = null;

        if (list1.nodeValue < list2.nodeValue) {
            mergeHead = list1;
            mergeHead.nextNode = mergeList(list1.nextNode, list2);
        } else {
            mergeHead = list2;
            mergeHead.nextNode = mergeList(list1,list2.nextNode);
        }

        return mergeHead;
    }

//测试方法
public static void main(String[] args) {
        ListNode node0 = new ListNode(0);
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        ListNode node6 = new ListNode(4);

        node0.nextNode = node2;
        node2.nextNode = node4;
        node4.nextNode = node6;

        node1.nextNode = node3;
        node3.nextNode = node5;

        ListNode merge = mergeList(node0,node1);
        while (merge != null){
            System.out.print(merge.nodeValue+",");
            merge = merge.nextNode;
        }
    }
算法思路

比较两个头节点,较小节点作为合并后的头节点。剩下的两个链表仍然有,递归地进行求解。

面试题26 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。二叉树节点定义如下:

class BinaryTreeNode{
    double nValue;
    BinaryTreeNode left;
    BinaryTreeNode right;
}
测试用例
  • 功能测试(输入两棵树,B是或者不是A的子结构;树中有或者没有相同的值的节点)
  • 特殊值测试(两棵树有一棵,或者两棵为null;两棵树相同)
实现代码
/**
     * 判断树A是否包含树B
     * @param A 树A
     * @param B 树B
     * @return 是否包含
     */
    public static boolean hasSubtree(BinaryTreeNode A,BinaryTreeNode B){
        boolean result =false;

        if (A != null && B != null){
            if (isEqual(A.nValue,B.nValue))
                result = doesTree1HasTree2(A,B);
            if (!result)
                result = hasSubtree(A.left,B);
            if (!result)
                result = hasSubtree(A.right,B);
        }
        return result;
    }

    /**
     * 在根节点相同的情况下,判断tree1是否包含tree2
     * @param tree1 树1
     * @param tree2 树2
     * @return 是否包含
     */
    private static boolean doesTree1HasTree2(BinaryTreeNode tree1, BinaryTreeNode tree2) {
        if (tree2 == null)
            return true;
        if (tree1 == null)
            return false;

        if (!isEqual(tree1.nValue,tree2.nValue))
            return false;
        return doesTree1HasTree2(tree1.left,tree2.left) && doesTree1HasTree2(tree1.right,tree2.right);
    }

    /**
     * 由于只需要判断是否想到能,不需要判断大小,所以将double转为String,
     * 再使用equals进行比较
     * @param a 数字1
     * @param b 数字2
     * @return 是否相等
     */
    private static boolean isEqual(double a, double b) {
        return String.valueOf(a).equals(String.valueOf(b));
    }

    public static void main(String[] args) {
        BinaryTreeNode tree1 = new BinaryTreeNode(0.501);
        BinaryTreeNode node1 = new BinaryTreeNode(1.501);
        BinaryTreeNode node2 = new BinaryTreeNode(2.501);
        tree1.left=node1;
        tree1.right = node2;

        BinaryTreeNode tree2 = new BinaryTreeNode(0.501);
        BinaryTreeNode node3 = new BinaryTreeNode(1.501);
        BinaryTreeNode node4 = new BinaryTreeNode(2.501);
        tree2.left=node3;
        //tree2.right=node4;

        System.out.println(hasSubtree(tree1,tree2));

    }
算法思路

遍历树A,找到与B的根节点值相同的点,然后判断以这两个节点为根的tree1和tree2,是否满足tree1包含tree2。是,则返回true。若不包含,则继续遍历A余下的节点,重复以上步骤。

面试题27 二叉树的镜像

请完成一个函数,输入一颗二叉树,该函数输出它的镜像。二叉树节点定义如下:

class BinaryTreeNode{
    int nValue;
    BinaryTreeNode left;
    BinaryTreeNode right;
}
测试样例
  • 功能测试(有左右子树的二叉树;只有左子树或右子树的二叉树)
  • 特殊值测试(二叉树为null;二叉树只有一个树根)
实现代码
 public void mirrorRecursively(BinaryTreeNode tree){
        if (tree == null)
            return ;
        if (tree.left == null && tree.right == null)
            return ;
        //交换左右子节点
        BinaryTreeNode temp = tree.left;
        tree.left = tree.right;
        tree.right = temp;

        if (tree.left!=null)
            mirrorRecursively(tree.left);
        if (tree.right != null)
            mirrorRecursively(tree.right);
    }

    /**
     * 在Java里面只有基本类型和按照下面这种定义方式的String是按值传递,
     * 其它的都是按引用传递。就是直接使用双引号定义字符串方式:String str = “Java私塾”;
     * @param args 参数
     */
    public static void main(String[] args) {
        BinaryTreeNode tree1 = new BinaryTreeNode(0.501);
        BinaryTreeNode node1 = new BinaryTreeNode(1.501);
        BinaryTreeNode node2 = new BinaryTreeNode(2.501);
        tree1.left=node1;
        tree1.right = node2;
        System.out.println(tree1.nValue);
        System.out.println(tree1.left.nValue);
        System.out.println(tree1.right.nValue);

        InterQuestions27 mirror = new InterQuestions27();
        mirror.mirrorRecursively(tree1);
        System.out.println(tree1.nValue);
        System.out.println(tree1.left.nValue);
        System.out.println(tree1.right.nValue);
    }
算法思路

从头节点开始,如果头节点有子树,那么交换左右子树,并递归地对子树进行同样的操作。如果没有子树,那么直接返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值