8. 字符串转换整数 (atoi)
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class Solution {
public:
void clearSpaces(std::string& str) {
size_t i = 0;
while (i < str.size() && str[i] == ' ') {
++i;
}
str.erase(0, i);
}

std::string readNum(const std::string& str) {
std::string reStr;
for (char s : str) {
if (isdigit(s)) {
reStr += s;
} else {
break;
}
}
return reStr;
}

void clearZero(std::string& str){
int i;
for(i = 0; i < str.size(); i++){
if(str[i] != '0'){
break;
}
}
str.erase(str.begin(),str.begin() + i);
}

unsigned long long strToNum(string& str, bool note) {
if(str.size() > 10){
if(note){
return INT32_MIN;
}
return INT32_MAX;
}
reverse(str.begin(), str.end());
unsigned long long chen = 1;
unsigned long long renum = 0;
for (auto s : str) {
int num = s - '0';
renum += chen * num;
chen *= 10;
if (renum >= INT32_MAX) {
if(note){
if(renum != INT32_MAX){
return INT32_MIN;
}else{
return -renum;
}
}
return INT32_MAX;
}
}
if(note){
return -renum;
}
return renum;
}

int myAtoi(std::string s) {
if (s.empty()) {
return 0;
}
bool isNegative = false;

// 去除字符串中无用的空格
clearSpaces(s);

// 如果第一个字符是字母,直接返回0
if (s.empty() || isalpha(s[0])) {
return 0;
}

// 处理正负号
if (s[0] == '-') {
isNegative = true;
s.erase(s.begin());
} else if (s[0] == '+') {
s.erase(s.begin());
}

// 读取完整的字符型数字
std::string numStr = readNum(s);
if (numStr.empty()) {
return 0;
}
// 排除前面的0
clearZero(numStr);

// 一切准备工作完成,开始把 字符串 转换为 数字
long long re = strToNum(numStr,isNegative);

return re;
}
};

这道题在力扣上的通过率非常低,当前是21.3%的通过率。我花费一个早上写的代码,最终还有一个测试用例没有通过,由于有其他事情暂时搁置。直到明天的早上,也就是今天早上,没过多久就把测试用例给过了,因为在搁置之后发现最后一个没有通过的测试用例可以通过判断数字是否过大而直接返回 INT32_MAX 或 INT32_MIN。

我们在进行字符串转换为数字之前,需要做很多预备工作,因为字符串里面有很多混杂的非数字字符:

  1. 如果是空字符,直接返回 0
  2. 去除字符串中无用的空格
  3. 如果第一个字符是字母,直接返回 0
  4. 记录正负号
  5. 读取完整的字符型数字
  6. 排除前面的 0

按照这个顺序来,我们的字符串就必然是全部由数字组成,就可以真正的去做字符串转数字的工作了

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
unsigned long long strToNum(string& str, bool note) {
if(str.size() > 10){
if(note){
return INT32_MIN;
}
return INT32_MAX;
}
reverse(str.begin(), str.end());
unsigned long long chen = 1;
unsigned long long renum = 0;
for (auto s : str) {
int num = s - '0';
renum += chen * num;
chen *= 10;
if (renum >= INT32_MAX) {
if(note){
if(renum != INT32_MAX){
return INT32_MIN;
}else{
return -renum;
}
}
return INT32_MAX;
}
}
if(note){
return -renum;
}
return renum;
}

按照题目要求,应该返回[INT32_MIN,INT32_MAX]区间内的数字,下面部分说明

  1. 如果字符串的长度大于 10,表明必然已经越界。如果这个数为负数,那么返回INT32_MIN,否则返回INT32_MAX
  2. 在循环取值计算的过程中,如果 renum 有 大于等于 INT32_MAX情况就要进行处理,避免后续没有意义的计算

下面这个地方容易让人不理解,但这实际是由 INT32_MIN(2147483648) 和 INT32_MAX(2147483647)不对称导致的。由于 INT32_MIN 绝对值 大于 INT32_MAX 绝对值,所以当 renum >= INT32_MAX 进入判断的时候,我们继续考虑当这个数为负数情况下,如果 renum = INT32_MAX,应该返回 -renum,而不能直接返回 INT32_MIN。

1
2
3
4
5
6
7
8
9
10
if (renum >= INT32_MAX) {
if (note) {
if (renum != INT32_MAX) {
return INT32_MIN;
} else {
return -renum;
}
}
return INT32_MAX;
}