天天看点

atoi函数_(Trivial)LeetCode 8—字符串转化为整数(atoi)

前言

首先,正式庆祝这是我在知乎专栏写作的第 20 篇题解(这里的下标从 1 开始)。当然,我本地库存的题解,还要比发布出来的多一倍。

关键字:字符串,状态机

归航return:(Trivial) LeetCode 6—Z 字形变换​zhuanlan.zhihu.com

atoi函数_(Trivial)LeetCode 8—字符串转化为整数(atoi)

归航return:(Trivial) LeetCode 46—全排列​zhuanlan.zhihu.com

atoi函数_(Trivial)LeetCode 8—字符串转化为整数(atoi)

Problem

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:

  • 如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
  • 假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
  • 该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0 。

提示:

  • 本题中的空白字符只包括空格字符 ' ' 。
  • 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31, 2^31 − 1]。如果数值超过这个范围,请返回 INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。

示例 1:

输入: "42"
输出: 42
           

示例 2:

输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
     我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
           

示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
           

示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
     因此无法执行有效的转换。
           

示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 
     因此返回 INT_MIN (−2^31) 。
           

8. 字符串转换整数 (atoi) - 力扣(LeetCode)​leetcode-cn.com

Solution

这道题目也没有很 tricky 的东西,完全就是需要自己足够

细心

来处理问题。关于这个问题,我的思路是这样的:

(1)维护一个最终结果是负数还是非负数的 int 变量,和一个存储所有数字的 queue,容器的 instantiation 是 char 类型;

(2)逐个扫描字符串中的元素,直到字符串被扫描完毕,或者遇到了第一个一定不合法的字符(不是数字,空格,或者正负号);

(3)如果扫描过程中第一次遇到了数字,空格,或者正负号,那么意味着之后的合法字符必须全部是数字,因此将 queue 中插入一个 0,或者是这个数字本身,用来表明这个更高的合法性要求(使用 queue 非空作为是否需要使用这个合法性要求的条件),而且如果是负号,应该将上述的非负数变量 flag 记作 -1,之后将所有的数字加入队列中来;

if (numQueue.empty())
           

(4)得到所有数字之后,维护一个 string 类型,将 queue 中的数字字符变成数字字符串,这里需要将开头的 0 去掉,因此维护一个 bool 变量来标记是否遇到过非零的数;

(5)第(4)步中得到的 string 就是以字符串形式存储的要求的整数的绝对值,根据非负数和负数的情况分别判定是否可能溢出,如果溢出就返回极端值,否则就将这个 string 从左到右地转化为整数即可,这里还需要一个模拟人类比较两个非负整数大小的辅助函数:

bool isFormerNumericallyBiggerThanLatter(const string &s1,const string &s2);
           

综上所述,代码如下:

class Solution {
public:
    int myAtoi(string str) {
        if (str.size() == 0)
            return 0;
        int res = 0;
        int isPositive = 1;
        queue<char>numQueue;
        for (int i = 0; i < str.size(); ++i){
            if (!mayBePossible(str[i])){
                break;
            }
            else{
                if (numQueue.empty()){
                    if (str[i] == ' '){
                        continue;
                    }
                    else if (str[i] == '-'){
                        isPositive = -1;
                        numQueue.push('0');
                    }
                    else if (str[i] == '+'){
                        numQueue.push('0');
                    }
                    else if (isdigit(str[i])){
                        numQueue.push(str[i]);
                    }
                }
                else{
                    if (!isdigit(str[i])){
                        break;
                    }
                    else
                        numQueue.push(str[i]);
                }
            }
        }
        bool isFirstNonZeroEmerged = 0;
        string resInString;
        while (!numQueue.empty()){
            if (isFirstNonZeroEmerged){
                resInString += numQueue.front();
                numQueue.pop();
            }
            else{
                if (numQueue.front() != '0'){
                    isFirstNonZeroEmerged = 1;
                    resInString += numQueue.front();
                }
                numQueue.pop();
            }
        }
        if (isPositive == 1 && isFormerNumericallyBiggerThanLatter(resInString, "2147483647"))
            return INT_MAX;
        if (isPositive==-1 && isFormerNumericallyBiggerThanLatter(resInString, "2147483648"))
            return INT_MIN;
        for (const char &ch:resInString){
            res = 10*res + isPositive*(ch-'0');
        }
        return res;
    }
private:
    bool mayBePossible(const char &ch){
        return (isdigit(ch) || ch == ' ' || ch == '-' || ch=='+');
    }
    bool isFormerNumericallyBiggerThanLatter(const string &s1, const string &s2){
        if (s1.size() != s2.size()){
            return s1.size() > s2.size();
        }
        for (int i = 0; i < s1.size(); ++i){
            if (s1[i] != s2[i])
                return s1[i] > s2[i];
        }
        return false;
    }
};
           

这个解法的耗时是 4ms,打败了 82.47% 的 C++ 提交。

在官方题解中还给出了一种使用状态机的思想写出来的代码,虽然耗时比我这种各种 if else 要长(20ms,打败了 8% 的 C++ 提交),而且使用了 long long 类型,但是

可读性

比我的明显要好,值得学习。

首先维护了一个 table 用来存储状态,分为四种:start 代表空格,signed 代表遇到了正负号,in_number 代表某个是数字,end 代表转化到了尾部状态了,容易观察到这几个状态的状态转移关系,结果在下方这个 unordered_map 中。

和有些长度固定的 unordered_map 问题一样,这道题目也可以使用二维 int 数组来代替 unordered_map 来优化时间

,但我就不狗尾续貂了,关键是学习这种思想。

复制代码如下:

class Automaton {
    string state = "start";
    //int state = 0;
    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"}}
    };
    /*
    const int table[4][4] = {
        {0,1,2,3},
        {3,3,2,3},
        {3,3,2,3},
        {3,3,3,3}
    }; //use a 2-dimensional array to represent the unordered map in the official solution*/
    int get_col(char c) {
        if (isspace(c)) return 0;
        if (c == '+' or c == '-') return 1;
        if (isdigit(c)) return 2;
        return 3;
    }
public:
    int sign = 1;
    long long ans = 0;

    void get(char c) {
        state = table[state][get_col(c)];
        if (state == "in_number") {
            ans = ans * 10 + c - '0';
            ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN);
        }
        else if (state == "signed")
            sign = c == '+' ? 1 : -1;
    }
};

class Solution {
public:
    int myAtoi(string str) {
        Automaton automaton;
        for (char c : str){
            automaton.get(c);
        }    
        return automaton.sign * automaton.ans;
    }
};
           

这个思路的 Java 实现:

class Solution {
    public int myAtoi(String str) {
        int curState = 0;
        int isPositive = 1;
        long res = 0;
        for (int i = 0; i < str.length(); ++i){
            char ch = str.charAt(i);
            curState = state[curState][getState(ch)];
            if (curState == 1){
                isPositive = (ch == '+') ? 1 : -1;
            }
            if (curState == 2){
                res *= 10L;
                res += ch-'0';
                if (res >= -(long)(Integer.MIN_VALUE))
                    break;
                /*If the absolute value of result 
                 is more than -INT_MIN, 
                 the result must be overflowed.*/
            }
            if (curState == 3){
                break;
            }
        }
        res *= isPositive;
        if (res >= Integer.MAX_VALUE){
            return Integer.MAX_VALUE;
        }
        else if (res <= Integer.MIN_VALUE){
            return Integer.MIN_VALUE;
        }
        return res;
    }
    final private int [][] state = {
        {0,1,2,3},
        {3,3,2,3},
        {3,3,2,3},
        {3,3,3,3}
        };
        /* 0 for beginning, 1 for the sign(positive or negative), 
2 for digits, 3 for terminating further input.*/
    private int getState(char ch){
        if (ch == ' '){
            return 0;
        }
        else if (ch == '+' || ch == '-'){
            return 1;
        }
        else if (isDigit(ch)){
            return 2;
        }
        return 3;
    }
}
           

和 C++ 语法不一样的几个点:

  • C++ 中求 string 的长度可以用

    str.size()

    或者

    str.length()

    ,但 Java 中必须使用

    str.length()

  • C++ 中获得字符串某个位置的结果可以使用

    str.operator[]()

    函数或者

    str.at()

    函数,区别在于前者不加入越界检查,后者会强制进行越界检查,但 Java 仅仅支持

    str.charAt()

    函数,且越界检查是强制的;
  • C++ 遍历一个

    string

    可以使用 range-based for loop:

    char ch : str

    ,但 Java 不允许(我尝试这么做编译错误);
  • C++ 的

    int

    类型最大值是

    INT_MAX

    ,而 Java 是

    Integer.MAX_VALUE

    ,C++ 中判定一个字符是否是数字可以直接

    isdigit(ch)

    (包含在

    <cctype>

    这个头文件中),但 Java 的用法是

    Character.isDigit(ch)

    ,前面的

    Character

    不可省略;
  • C++ 中允许

    long

    类型到

    int

    的隐式类型转换—当然更好的做法是:

    static_cast<int>(someLongVariable)

    ,但 Java 中必须直接指出这一点,Java 不做说明的情况下只允许 implicit type casting,向上转换,而不允许 implicit type conversion 向下转换。

EOF。

继续阅读