1. 程式人生 > 實用技巧 >LeetCode初級演算法之字串:8 字串轉換整數 (atoi)

LeetCode初級演算法之字串:8 字串轉換整數 (atoi)

字串轉換整數 (atoi)

題目地址:https://leetcode-cn.com/problems/string-to-integer-atoi/

請你來實現一個 atoi 函式,使其能將字串轉換成整數。

首先,該函式會根據需要丟棄無用的開頭空格字元,直到尋找到第一個非空格的字元為止。接下來的轉化規則如下:

  • 如果第一個非空字元為正或者負號時,則將該符號與之後面儘可能多的連續數字字元組合起來,形成一個有符號整數。

  • 假如第一個非空字元是數字,則直接將其與之後連續的數字字元組合起來,形成一個整數。

  • 該字串在有效的整數部分之後也可能會存在多餘的字元,那麼這些字元可以被忽略,它們對函式不應該造成影響。

注意:假如該字串中的第一個非空格字元不是一個有效整數字符、字串為空或字串僅包含空白字元時,則你的函式不需要進行轉換,即無法進行有效轉換。

在任何情況下,若函式不能進行有效的轉換時,請返回 0 。

提示:

本題中的空白字元只包括空格字元 ' ' 。
假設我們的環境只能儲存 32 位大小的有符號整數,那麼其數值範圍為 [−231, 231 − 1]。如果數值超過這個範圍,請返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 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 (−231) 。

解法一

嚴格來說這個問題其實沒有太多考察演算法的內容不涉及資料結構、演算法思維的選取。它是一個數據的效驗篩選,更多的像開發場景中的一些原始資料的處理引數的篩選。所以不難但有較多的判斷語句涉及一些細節問題需要仔細對比

主體

  1. 去掉前導空格
  2. 處理正負號
  3. 識別數字

邊界

  1. 整數溢位
  2. 索引越界

細節

  1. 無效轉換返回0
public int myAtoi(String str) {
    char[] chars = str.toCharArray();
    int n = chars.length;
    int index = 0;
    //1.去掉前導空格
    while (index < n && chars[index] == ' ') {
        index++;
    }
    //2.符號處理
    int sign = 1;
    if(index < n && chars[index] == '+'){
        index++;
    }else if(index < n && chars[index] == '-'){
        sign = -1;
        index++;
    }
    //3.拼接數字
    int result = 0;
    while (index < n && chars[index]-'0' >= 0 && chars[index]-'0' <= 9) {
        int num = chars[index] - '0';
        if (result > (Integer.MAX_VALUE - num) / 10) {
            // 本來應該是 result * 10 + num > Integer.MAX_VALUE
            // 但是 *10 和 + num 都有可能越界,所以在上面逆向判斷
            return sign < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        }
        result = result * 10 + num;
        index++;
    }
    return result * sign;
}

解法二:正則表示式

這樣一個篩選串的過程我們除了自己去去遍歷判斷,也可以使用正則表示式的方式。用到Java類庫的相關工具但本質上還是和解法一是一樣的。需要引入java.util.regex包下的Pattern與Matcher

public int myAtoi(String str) {
    str = str.trim();
    Pattern pattern = Pattern.compile("[-+]??[0-9]++");
    Matcher matcher = pattern.matcher(str);
    if (matcher.lookingAt()) {
        String num = str.substring(0, matcher.end());
        try {
            return Integer.parseInt(num);
        } catch (NumberFormatException nfe) {
            return num.charAt(0) == '-' ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        }
    }
    return 0;
}

解法三:有限狀態自動機

正則表達的所匹配的所有字串構成都可以用有限自動機識別,其實上面解法的每個過程判定就是一個有限自動機的每個狀態。從去除空格階段到取符號階段到數字階段到結束。和KMP一樣都可解決字串匹配相關問題

狀態機裡面有 4 個概念:

  • State ,狀態。一個狀態機至少要包含兩個狀態。
  • Event ,事件。事件就是執行某個操作的觸發條件或者口令。
  • Action ,動作。事件發生以後要執行動作。
  • Transition ,變換。也就是從一個狀態變化為另一個狀態。

套到這道題裡就是我們的程式在每個時刻有一個狀態 s ,每次從序列中輸入一個字元 c ,並根據字元 c 轉移到下一個狀態 s' 。這樣,我們只需要建立一個覆蓋所有情況的從 sc 對映到 s' 的表格即可解決題目中的問題。

答案中給出的示例圖是這樣的:

用圖表來表示:

我又畫了下面一張圖和上面兩個同義。(0表示狀態start,1表示狀態signed,2表示狀態int_number,3表示狀態end,'其他'表示除[空格、正負號、數字]之外的字元)

意思就是說依次輸入字元。首先字元是進入狀態0(start)如果字元是空格那麼下個仍然進入狀態0.如果是其他則進入狀態3(end)結束處理。那我們就可以實現這樣一個自動機

class Automata{
    //下次的狀態
    private int state=0;
    //狀態表
    private int[][] table={{0,1,2,3},{3,3,2,3},{3,3,2,3},{3,3,3,3}};
    //字元的四種情況
    public int getCharState(char c){
        if(c==' ')return 0;
        if(c=='+'|| c=='-')return 1;
        if(Character.isDigit(c))return 2;
        return 3;
    }
    int result = 0; //結果
    int sign = 1; //符號
    public void input(char c){
        //通過這次的字元設定下個狀態
        state=table[state][getCharState(c)];
        //state == 0 直接過
        if(state==1 &&c=='-') sign=-1;
        if(state==2){
            int num = c - '0';
            //除了通過當前狀態和字元來判斷下個去往狀態
            //溢位發生時其實也應該把狀態設為3(end)
            if (result > (Integer.MAX_VALUE - num) / 10) {
                result = sign < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                sign = 1;
                state = 3;
            }else result = result * 10 + num;
        }
        //state == 3 直接過
    }
}

然後就是使用我們的自動機解題啦

public int myAtoi(String str) {
    Automata auto=new Automata();
    char[] chars=str.toCharArray();
    for(char c:chars){
        auto.input(c);
    }
    return auto.sign*auto.result;
}

或者state為3這邊也直接結束不去掉用空方法

public int myAtoi(String str) {
    Automata auto=new Automata();
    char[] chars=str.toCharArray();
    int i = 0;
    while(i<chars.length && auto.state != 3){
        auto.input(chars[]);
        i++;
    }
    return auto.sign*auto.result;
}

總結

就這題本身來講是比較簡單的,主要是處理細節問題可能很多小夥伴在像解法一的處理過程中沒有組織清晰導致雖然能解出題但有很多程式碼是冗餘的或者重複判斷了的地方。最終解法一其實也不過兩個if而已沒有像傳聞中那樣說很多ifelse,三個處理第一個空格處理是迴圈處理,第二符號只有首位所以單個if,第三個數字也是迴圈處理。因此三個必要的判斷兩個是while一個是if。最後還加上一個溢位的判斷。除此之外這題也有學習到的地方,第一是對正則的回顧,第二是引出有限狀態自動機這樣一個計算機思想也是等於又溫習下離散數學。對於這題沒有什麼演算法思維理清思路書寫下來就是解法一的這樣一個過程。因此這一題的意圖可能就是讓我們學習到有限狀態自動機封裝這樣一個機器或者規則往這個規則裡輸入語言串它去給我們輸出相應的結果。