博客 / 詳情

返回

劍指offer-53、表達數值的字符串

題⽬描述

請實現⼀個函數⽤來判斷字符串str是否表示數值(包括科學計數法的數字,⼩數和整數)。科學計數法的數字(按順序)可以分成以下⼏個部分:

  1. 若⼲空格
  2. ⼀個整數或者⼩數
  3. (可選)⼀個 ' e ' 或 ' E ' ,後⾯跟着⼀個整數(可正可負)
  4. 若⼲空格

⼩數(按順序)可以分成以下⼏個部分:

  1. 若⼲空格
  2. (可選)⼀個符號字符('+' 或 '-')
  3. 可能是以下描述格式之⼀:

    1. ⾄少⼀位數字,後⾯跟着⼀個點 '.'
    2. ⾄少⼀位數字,後⾯跟着⼀個點 '.' ,後⾯再跟着⾄少⼀位數字
    3. ⼀個點 '.' ,後⾯跟着⾄少⼀位數字
  4. 若⼲空格

整數(按順序)可以分成以下⼏個部分:

  1. 若⼲空格
  2. (可選)⼀個符號字符(' + ' 或 ' - ')
  3. ⾄少⼀位數字
  4. 若⼲空格

例如,字符串["+100","5e2","-123","3.1416","-1E-16"] 都表示數值。

但是["12e","1a3.14","1.2.3","+-5","12e+4.3"] 都不是數值。

提示:

  1. 1 <= str.length <= 25
  2. str 僅含英⽂字⺟(⼤寫和⼩寫),數字(0-9),加號 '+' ,減號 '-' ,空格 ' ' 或者點 '.' 。
  3. 如果懷疑⽤例是不是能表示為數值的,可以使⽤python 的print(float(str)) 去查看

示例1
輸⼊:"123.45e+6"
返回值:true

示例2
輸⼊:"1.2.3"
返回值:false

思路及解答

暴力分析拆解

主要是分析好判斷分⽀,可以定義⼏個變量:

  • hashNum : 是否已經有數字
  • hashE :是否已經有E
  • hasSign :是否已經有符號
  • hasDot :是否已經有⼩數點

⾸先,初始化當前的索引index =0 ,字符串頭部的空格需要跳過。

  • 循環判斷索引是否在有效的範圍內:

    • 循環判斷是否是數字,是數字則更新hasNum = true ,並且索引後移,直到不是數字的時候,跳出循環。
  • 跳出循環後,需要判斷當前的index 是否合法,不合法直接break
  • 取出當前索引的字符c :

    • 如果c 是e 或者E :

      • 如果前⾯已經出現過E ,或者前⾯沒有數字,直接返回false
      • 否則, hasE 置為true ,其他的置為false ,也就是E後⾯可以繼續出現符號數字和⼩數點了
    • 如果c 是“ + ”或者“ - ”:

      • 前⾯如果已經出現過數字或者符號或者⼩數點,都不是合法的
      • 否則hasSign 置為true ,表示符號出現過
    • 如果c 是⼩數點“ . ”

      • 如果前⾯已經有⼩數點或者有E出現了,那麼就是⾮法的,返回false
      • 否則hasDot 置為true
    • 如果c 為空格,直接跳出循環
    • 否則,直接返回false
  • 最後也需要跳過空格
  • 最後判斷是否合法的條件是:是否到達最後⼀個字符,並且出現過數字
public boolean isNumeric(String str) {
    int size = str.length();
    int index= 0 ;
    // 默認全部是false
    boolean hashNum=false ,hasE=false ,hasSign=false ,hasDot=false;
    // 跳過空格
    while(index<size&&str.charAt(index)==' '){
        index++;
    }
    
    while(index<size){
        while(index<size&&str.charAt(index)>='0'&& str.charAt(index)<='9'){
            index++;
            // 表示前⾯有數字
            hashNum = true;
        }
- 
        // 到末尾直接跳出
        if(index==size){
            break;
        }
- 
        char c = str.charAt(index);
        if(c=='e'||c=='E'){
            // 前⾯有E或者沒有數字在前⾯
            if(hasE||!hashNum){
                return false;
            }
            hasE = true;
            // 出現E了後⾯⼜可以出現數字了
            hashNum = false;
            hasSign = false;
            hasDot = false;
        }else if(c=='+'||c=='-'){
            if(hasSign||hashNum||hasDot){
                return false;
            }
            hasSign = true;
        }else if(c=='.'){
            if(hasDot||hasE){
                return false;
            }
            hasDot =true;
        }else if(c==' '){
            break;
        }else{
            return false;
        }
        index++;
    }
    // 跳過空格
    while(index<size&&str.charAt(index)==' '){
        index++;
    }
    return hashNum &&index==size;
}

這道題,其實本質是狀態的切換,最最重要的⼀點,是 E 出現之後,其實⼩數點和符號,和數字,都是可以再出現的,可以理解為 E 就是⼀個分割線。

正則表達式

直接藉助正則表達式進⾏匹配,但是並不太推薦這種解法:

public class Solution {

    public boolean isNumeric (String str) {
        // 核心正則表達式:處理空格、符號、小數、指數部分
        
        // ^表示開頭 $ 表示結尾 java中兩個\\ 代表⼀個\
        // \\s*開頭可能有空格
        // * 零次或多次匹配前⾯的字符或⼦表達式
        // ?零次或⼀次匹配前⾯的字符或⼦表達式
        // + ⼀次或多次匹配前⾯的字符或⼦表達式
        // [] 字符集。匹配包含的任⼀字符
        // (:? )匹配 pattern 但不捕獲該匹配的⼦表達式,即它是⼀個⾮捕獲匹配
        String p = "^\\s*[+-]?(\\d*\\.\\d+|\\d+(\\.\\d*)?)(?:[eE][+-]?\\d+)?$";
        return Pattern.matches(p,str);
    }
}
  • O(n)時間複雜度
  • O(1)空間複雜度

有限狀態機

使用確定有限狀態機(DFA)來精確建模數值的判斷過程。

有限狀態機法:通過狀態轉移精確控制數值格式。定義9種狀態,根據輸入字符進行狀態轉移

public boolean isNumberDFA(String str) {
    if (str == null) return false;
    
    // 狀態定義:0-8共9種狀態
    // 0: 起始空格 1: 符號 2: 整數數字 3: 小數點前無數字 
    // 4: 小數點後有數字 5: 指數e 6: 指數符號 7: 指數數字 8: 結尾空格
    int state = 0; // 初始狀態
    
    // 狀態轉移表
    int[][] transitionTable = {
        // 空格 符號 數字 小數點 e/E 其他
        {0,   1,   2,   3,   -1, -1}, // 狀態0: 起始空格
        {-1, -1,   2,   3,   -1, -1}, // 狀態1: 符號
        {8,  -1,   2,   4,    5, -1}, // 狀態2: 整數數字
        {-1, -1,   4,   -1,  -1, -1}, // 狀態3: 小數點前無數字
        {8,  -1,   4,   -1,   5, -1}, // 狀態4: 小數點後有數字
        {-1,  6,   7,   -1,  -1, -1}, // 狀態5: 指數e
        {-1, -1,   7,   -1,  -1, -1}, // 狀態6: 指數符號
        {8,  -1,   7,   -1,  -1, -1}, // 狀態7: 指數數字
        {8,  -1,  -1,   -1,  -1, -1}  // 狀態8: 結尾空格
    };
    
    for (char c : str.toCharArray()) {
        int inputType = getInputType(c);
        if (inputType == -1) return false;
        
        state = transitionTable[state][inputType];
        if (state == -1) return false;
    }
    
    // 可接受的狀態:數字相關狀態(2,4,7,8)
    return state == 2 || state == 4 || state == 7 || state == 8;
}

// 將字符分類為狀態機輸入類型
private int getInputType(char c) {
    switch (c) {
        case ' ': return 0; // 空格
        case '+': case '-': return 1; // 符號
        case '0': case '1': case '2': case '3': case '4': 
        case '5': case '6': case '7': case '8': case '9': 
            return 2; // 數字
        case '.': return 3; // 小數點
        case 'e': case 'E': return 4; // 指數符號
        default: return 5; // 其他字符
    }
}
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.