微软面试题_中文字符串转换为数字
LeetCode 微软
Contents
- 题目
- 解答
- 方法1:单调栈
- 复杂度分析
- 方法2:递归
- 复杂度分析
题目
解答
方法1:单调栈
参考把中文表示的数字转成阿拉伯数字 - java
- 遍历一次字符串,判断字符串中是否包含单位,这两种情况下的处理逻辑是不同的
- 再遍历一次字符串,计算数字
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:递归
递归的核心在于如何把整体问题分解为很多结构相似的子问题。
本题中的整体问题是:求出整个字符串表示的数值。
分解问题的思路是:
- 找出整个字符串中最大的单位
maxUnit
- 以及在字符串中的索引
maxUnitIdx
- 以
maxUnitIdx
- 作为分界点,可以把整个字符串
[lo...hi]
- 划分为三个部分
beforeUnit
- 区间为
[lo...maxUnitIdx - 1]
- ,即单位之前的数值
maxUnit
- 即当前区间
[lo...hi]
- 当中的最大单位的数值
afterUnit
- 区间为
[maxUnitIdx+1...hi]
- 即单位之后的数值
[lo...hi]
- 区间的数值 =
beforeUnit * maxUnit + afterUnit
- ,其中的
beforeUnit
- 和
afterUnit
- 就是子问题,需要进一步调用递归函数求解。
至此,我们已经搞清楚如何分解问题,接下来需要进一步确定递归函数的一系列要素:
- 递归函数签名(即输入输出)
- 输入字符串,以及一个由左右边界表示的范围;输出这个范围内的字符串表示的值。
private static long recur(char[] arr, int lo, int hi)
- 终止条件
- 终止条件是
lo == hi
- ,也就是区间内只有一个字符,返回这个字符表示的数字。
- 可以推断,如果输入是合法数值的话,这种区间内必然是一个表示数字的字符(因为单位不可能单独出现,必然会有单位前的一个系数,如果有单位的话,不可能在递归调用的区间内,因为递归调用都是把单位的索引排除在外的)。
- 返回值
beforeUnit * maxUnit + afterUnit
- ,
beforeUnit
- 和
afterUnit
- 需要进一步调用递归函数求解。
- 注意特殊的情况:
beforeUnit
- 和
afterUnit
- 两个部分都可能是空的,二者是空区间对应的数值是不同的给,如果
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)
,递归每次递归调用都会维护一个新的字符数组