微软面试题_中文字符串转换为数字

微软面试题_中文字符串转换为数字

Contents

题目


解答

方法1:单调栈

参考把中文表示的数字转成阿拉伯数字 - java

  1. 遍历一次字符串,判断字符串中是否包含单位,这两种情况下的处理逻辑是不同的
  2. 再遍历一次字符串,计算数字
public class zhToNumber {
    public static void main(String[] args) {
        System.out.println(convert("八亿四千八百卅万零二千六百廿五"));
        System.out.println(convert("三零四五七八九"));
    }

    static String digit = "零一二三四五六七八九";
    static String unit = "十廿卅百千万亿";
    static int[] unitVal = new int[]{10, 20, 30, 100, 1000, 10000, 100000000};

    public static long convert(String zh) {
        long num = 0;
        boolean containUnit = false;
        Stack<Integer> stack = new Stack<>();
        char[] arr = zh.toCharArray();
        for (char c : arr) {
            if (unit.indexOf(c) != -1) containUnit = true;
        }
        //包含单位的情况,单调栈
        if (containUnit) {
            for (char cur : arr) {
                if (cur == '零') continue;
                int unitIdx = unit.indexOf(cur);
                //如果当前字符是数字,直接将其入栈,等待之后遇到单位时,与单位相乘
                if (unitIdx == -1) {
                    stack.push(digit.indexOf(cur));//这个方法用的非常妙!
                }
            /*如果当前字符是单位,则需要将这个单位乘到修饰这个单位的数字上
            修饰的数字满足两个条件:比当前单位数值小;比当前单位先入栈。*/
                else {
                    int curUnitVal = unitVal[unit.indexOf(cur)];//当前单位对应的值
                    int stackSum = 0;//stack当中修饰当前单位的数值求和
                    while (!stack.isEmpty() && stack.peek() < curUnitVal) {
                        stackSum += stack.pop();
                    }
                    if (stackSum == 0) {//没有修饰当前单位的数值,直接把当前单位的值入栈
                        stack.push(curUnitVal);
                    } else {
                        stack.push(curUnitVal * stackSum);
                    }
                }
            }
            while (!stack.isEmpty()) {
                num += stack.pop();
            }
            return num;
        } else {
            //不包含单位的情况,直接遍历数字
            for (char cur : arr) {
                if (digit.indexOf(cur) != -1) {
                    int curVal = digit.indexOf(cur);
                    num = num * 10 + curVal;
                }
            }
            return num;
        }
    }
}

复杂度分析

时间复杂度:O(n),遍历两轮
空间复杂度:O(n),需要用一个辅助栈保存临时结果

方法2:递归

递归的核心在于如何把整体问题分解为很多结构相似的子问题。
本题中的整体问题是:求出整个字符串表示的数值。
分解问题的思路是:

  1. 找出整个字符串中最大的单位maxUnit以及在字符串中的索引maxUnitIdx
  2. maxUnitIdx作为分界点,可以把整个字符串[lo...hi]划分为三个部分
    • beforeUnit区间为[lo...maxUnitIdx - 1],即单位之前的数值
    • maxUnit即当前区间[lo...hi]当中的最大单位的数值
    • afterUnit区间为[maxUnitIdx+1...hi]即单位之后的数值
  3. [lo...hi]区间的数值 = beforeUnit * maxUnit + afterUnit,其中的beforeUnitafterUnit就是子问题,需要进一步调用递归函数求解。

至此,我们已经搞清楚如何分解问题,接下来需要进一步确定递归函数的一系列要素:

  • 递归函数签名(即输入输出)
    • 输入字符串,以及一个由左右边界表示的范围;输出这个范围内的字符串表示的值。
    • private static long recur(char[] arr, int lo, int hi)
  • 终止条件
    • 终止条件是lo == hi,也就是区间内只有一个字符,返回这个字符表示的数字。
    • 可以推断,如果输入是合法数值的话,这种区间内必然是一个表示数字的字符(因为单位不可能单独出现,必然会有单位前的一个系数,如果有单位的话,不可能在递归调用的区间内,因为递归调用都是把单位的索引排除在外的)。
  • 返回值
    • beforeUnit * maxUnit + afterUnitbeforeUnitafterUnit需要进一步调用递归函数求解。
    • 注意特殊的情况:beforeUnitafterUnit两个部分都可能是空的,二者是空区间对应的数值是不同的给,如果beforeUnit是空,对应1;如果afterUnit是空,对应0。

最终,还有一类特殊情况需要处理,就是字符'零'的情况。在我们的实现中,如果出现'零'会导致无限递归,无法跳出递归。而事实上中文数字里的'零'是没有信息量,可以省去的(比如一百零一和一百一是一样的),所以在进入递归函数之前,先使用replace()方法把所有的'零'都去除掉。

下面的代码中没有考虑不带单位的情况,是因为不带单位的情况跟方法1中写法一样,就不重复写了。

public class zhToNumber_2 {
    public static void main(String[] args) {
        String test1 = "二亿三千四百五十万卅千零廿九";
        test1 = test1.replace("零", "");//去除所有的零
        char[] arr = test1.toCharArray();
        System.out.println(recur(arr, 0, arr.length - 1));
    }

    private static String digit = "零一二三四五六七八九";
    private static String unit = "十廿卅百千万亿";
    private static int[] unitVal = new int[]{10, 20, 30, 100, 1000, 10000, 100000000};

    private static long recur(char[] arr, int lo, int hi) {
        //递归终止条件:遇到只有一个字符的情况,并且这个字符表示数字,那么可以直接返回这个数字
        if (lo == hi && digit.indexOf(arr[lo]) != -1) {
            return digit.indexOf(arr[lo]);
        }
        //找出arr[lo...hi]中最大的单位,以及最大单位的索引
        int maxUnit = 0, maxUnitIdx = -1;
        for (int i = lo; i <= hi; i++) {
            char cur = arr[i];
            int curUnit = unit.indexOf(cur);
            if (curUnit != -1 && unitVal[curUnit] > maxUnit) {
                maxUnit = unitVal[curUnit];
                maxUnitIdx = i;
            }
        }
        //以最大的单位为分割点,将字符串分为三个部分,[beforeUnit][maxUnit][afterUnit],返回值为:beforeUnit * maxUnit + afterUnit
        //单位之前如果是空字符串,那么maxUnit应该乘以1
        long beforeUnit = lo > maxUnitIdx - 1 ? 1 : recur(arr, lo, maxUnitIdx - 1);
        //单位之后如果是空字符串,那么beforeUnit * maxUnit应该加上0
        long afterUnit = maxUnitIdx + 1 > hi ? 0 : recur(arr, maxUnitIdx + 1, hi);
        //返回值:arr[lo...hi]字符串表示的数值
        return beforeUnit * maxUnit + afterUnit;
    }
}

复杂度分析

时间复杂度:O(m * n),m为递归调用次数,n为每次递归调用的区间长度的均值,因为每次递归调用都要遍历长度为n的字符数组
空间复杂度:O(m * n),递归每次递归调用都会维护一个新的字符数组

posted @ 2021-05-09 14:41  Howfar's  阅读(1111)  评论(0编辑  收藏  举报