Leetcode8. 字符串转换整数 (atoi)

题目描述

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

  • 读入字符串并丢弃无用的前导空格
  • 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  • 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  • 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  • 如果整数数超过 32 位有符号整数范围 [231,2311][-2^{31},2^{31}-1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 231-2^{31} 的整数应该被固定为 231-2^{31} ,大于23112^{31}-1 的整数应该被固定为 23112^{31}-1
  • 返回整数作为最终结果。

注意:

  • 本题中的空白字符只包括空格字符 ' '
  • 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"42"(读入 "42")
^
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。

示例 2:

1
2
3
4
5
6
7
8
9
10
11
输入:s = "   -42"
输出:-42
解释:
第 1 步:" -42"(读入前导空格,但忽视掉)
^
第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:" -42"(读入 "42")
^
解析得到整数 -42 。
由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。

示例 3:

1
2
3
4
5
6
7
8
9
10
11
输入:s = "4193 with words"
输出:4193
解释:
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。

示例 4:

1
2
3
4
5
6
7
8
9
10
11
输入:s = "words and 987"
输出:0
解释:
第 1 步:"words and 987"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"words and 987"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"words and 987"(由于当前字符 'w' 不是一个数字,所以读入停止)
^
解析得到整数 0 ,因为没有读入任何数字。
由于 0 在范围 [-231, 231 - 1] 内,最终结果为 0 。

示例 5:

1
2
3
4
5
6
7
8
9
10
11
输入:s = "-91283472332"
输出:-2147483648
解释:
第 1 步:"-91283472332"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"-91283472332"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:"-91283472332"(读入 "91283472332")
^
解析得到整数 -91283472332 。
由于 -91283472332 小于范围 [-231, 231 - 1] 的下界,最终结果被截断为 -231 = -2147483648 。

提示:

  • 0 <= s.length <= 200
  • s 由英文字母(大写和小写)、数字(0-9)、' ''+''-''.' 组成

解题思路

这道题是一道字符串处理的题,通常有两种处理方法。一种是常规的字符串处理,但是往往会因为边缘条件写出又臭又长的代码;另一种是利用自动状态机来处理字符串,代码简洁可读性高。

下面分别介绍一下两种方法。

方法一:普通字符串处理法

这种方法就是根据字符串处理的规则,用代码进行模拟。

具体的步骤如下:

  • 首先,将空格越过,将第一个非空格的下标记录下来,记为start

  • 接着循环处理剩下的每一个字符:

    • 如果start位的字符为-,那么将标记是否为负号的标志isMin置为true,指针i后移;
    • 如果start位的字符为+,那么将标记是否为负号的标志isMin置为false,指针i后移;
    • 如果当前位的字符是数字,分正负号对答案ans进行越界判断,然后更新ans的值;
    • 如果是其他情况,直接中止循环,返回0。

这其中的一个难点就是如何进行溢出判断,方法有两种:

  1. 将转化为的ans直接用long long类型存储,不用int类型,这样的话就不会发生溢出,只需要和int类型可以存储的上界和下界判断大小就可以了。这种方法简单粗暴,有点降维打击的意思。
  2. ansint类型存储,每一次更新ans的时候都必须进行溢出判断。判断的语句如下:
1
2
3
if(ans<INT32_MIN/10||(ans==INT32_MIN/10&&(s[i]-'0')>-(INT32_MIN%10)))

if(ans>INT32_MAX/10||(ans==INT32_MAX/10&&(s[i]-'0')>INT32_MAX%10))

以判断是否超过上界为例。首先判断是否超过INT32_MAX/10,如果超过,说明输入的字符串表示的数的位数已经超过了上界,发生了溢出;还有一种情况是,输入的字符串表示的数的位数与上界的位数相等,且除了最后一位无法判断以外其余位数都相等,那么我们就需要判断最后一位与上界的最后一位的大小来判断是否溢出。判断下界的溢出同理。

方法二:有限自动状态机

我们的程序在每个时刻有一个状态 s,每次从序列中输入一个字符 c,并根据字符 c 转移到下一个状态 s'。这样,我们只需要建立一个覆盖所有情况的从 s c 映射到 s' 的表格即可解决题目中的问题。

本题可以建立如下图所示的自动机:

我们也可以用下面的表格来表示这个自动机:

’ ’ +/- number other
start start signed in_number end
signed end end in_number end
in_number end end in_number end
end end end end end

然后我们只需要实现这个状态表就可以了。

示例代码

方法一:普通字符串处理法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int myAtoi(string s)
{
int ans=0;
bool isMin=false;
int i=0;
while(i<s.size()&&s[i]==' ')
{
++i;
}
int start=i;
for(;i<s.size();++i)
{
if(s[i]=='-'&&i==start)
{
isMin=true;
}
else if(s[i]=='+'&&i==start)
{
isMin=false;
}
else if(s[i]>='0'&&s[i]<='9')
{
if(isMin)

{
if(ans<INT32_MIN/10||(ans==INT32_MIN/10&&(s[i]-'0')>-(INT32_MIN%10))) return INT32_MIN;
ans=ans*10-(s[i]-'0');
}
else{
// cout<<s[i]<<" "<<ans<<" "<<INT32_MAX/10<<" "<<INT32_MAX%10<<endl;
if(ans>INT32_MAX/10||(ans==INT32_MAX/10&&(s[i]-'0')>INT32_MAX%10)) return INT32_MAX;
//ans=ans*10+s[i]-'0';
ans=ans*10+(s[i]-'0');
}
}
else{
break;
}

}
return ans;
}

这里面有一个小坑

这一行语句ans=ans*10+(s[i]-'0');不能去掉括号,去掉括号后本地IDE没有任何问题,但是leetcode会报错!!!

方法二:有限自动状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Automaton{
string stat="start";
unordered_map<string,vector<string>>table={
{"start",{"start","signed","in_number","end"}},
{"signed",{"end","end","in_number","end"}},
{"in_number",{"end","end","in_number","end"}},
{"end",{"end","end","end","end"}}
};

int get_col(char c)
{
if(isspace(c)) return 0;
if(c=='+'||c=='-') return 1;
if(isdigit(c)) return 2;
return 3;
}

public:
bool isEnd=false;
int sign=1;
long long ans=0;

void deal(char c)
{
stat=table[stat][get_col(c)];
if(stat=="in_number")
{
ans=ans*10+(c-'0');
ans=sign==1?min(ans,(long long)INT32_MAX):min(ans,-(long long)INT32_MIN);
}
else if(stat=="signed")
{
sign=c=='+'?1:-1;
}
else if(stat=="end")
{
isEnd=true;
}

}


};

int myAtoi(string s)
{
Automaton automaton;
for(char c:s)
{
automaton.deal(c);
if(automaton.isEnd) break;
}
return automaton.sign*automaton.ans;

}