天天看点

[leetCode]剑指 Offer 20. 表示数值的字符串扫描法状态机

[leetCode]剑指 Offer 20. 表示数值的字符串扫描法状态机

扫描法

数字格式可以使用 A[.[B]][E|eC]表示A和C都是整数(可以有符号),而B是无符号整数

class Solution {
    /**
    * 由于java基本数据类型只能值传递,所以自定义一个引用类型
    * 该类型保存了需要判别的字符串和一个该字符串的指针
    */
    class IdxString {
        private String s;
        private Integer i;
        public IdxString(String s, Integer i){
            this.s = s;
            this.i = i;
        }
        //指针i++
        public void increaseI(){
            this.i++;
        }
        public String getS(){
            return this.s;
        }
        public Integer getI(){
            return this.i;
        }
        //返回当前指针i处s的字符
        public char charAtI(){
            return this.s.charAt(this.i);
        }
    }

    public boolean isNumber(String s) {
        if(s == null)
            return false;
        s = s.trim();//有"1 ", " 0"的情况直接trim掉
        if(s.length() == 0) return false;//考虑 s 类似" ",只有空格的情况 
        int len = s.length();
        IdxString str = new IdxString(s, 0);
        //先扫描小数点之前是否为数字
        boolean isNumeric = sanInteger(str, len);
        //扫描小数点之后是否为数字
        if(str.getI() < len && str.charAtI() == '.'){
            str.increaseI();//跳过小数点
            //使用 || 的原因: 
            //1、会出现 .123的情况
            //2、会出现 123.的情况
            isNumeric = scanUnsignedInteger(str,len) || isNumeric;
        }
        //扫描e or E 之后是否为数字
        if(str.getI() < len && (str.charAtI() == 'e' || str.charAtI() == 'E')){
            str.increaseI();
            //下面使用 && 是因为:
            // 1、 e|E 前没有数字时不表示数字如 .e1、e1;
            // 2、 e|E 后没有整数时不能表示数字如 12e、12e+5.4
            isNumeric = isNumeric && sanInteger(str, len);
        }
        return isNumeric && str.getI() == len;//只有指针到最底,才代表扫描完毕
    }

    private boolean sanInteger(IdxString str, int len) {
        String s = str.getS();
        Integer i = str.getI();
        if( i < len && (s.charAt(i) == '+' || s.charAt(i) == '-'))
            str.increaseI();
        return scanUnsignedInteger(str, len);
    }

    private boolean scanUnsignedInteger(IdxString str, int len) {
        int before = str.getI();
        while(str.getI() < len && str.charAtI() >='0' && str.charAtI() <= '9'){
            str.increaseI();
        }
        return str.getI() > before;
    }
}
           

状态机

class Solution {
    public boolean isNumber(String s) {
        Map[] status = {
            new HashMap<>() {{put(" ", 0); put("sign", 1); put("digit", 2); put(".", 4);}}, // 0. 空格
            new HashMap<>() {{put("digit", 2); put(".", 4);}},                               // 1. 符号
            new HashMap<>() {{put("digit", 2); put(".", 3); put("e", 5); put(" ", 8);}},    // 2. 小数点前的数字
            new HashMap<>() {{put("digit", 3); put("e", 5); put(" ", 8);}},                 // 3. 小数点以及小数点之后的数字
            new HashMap<>() {{put("digit", 3);}},                                           // 4. 小数点左侧无数字时,小数点及数字
            new HashMap<>() {{put("sign", 6); put("digit", 7);}},                           // 5. 幂符号 e / E
            new HashMap<>() {{put("digit", 7);}},                                           // 6. 幂符号之后的正负号 
            new HashMap<>() {{put("digit", 7); put(" ", 8);}},                              // 7. 幂符号之后的数字
            new HashMap<>() {{put(" ", 8);}},                                               // 8. 空格
        };
        int curState = 0;
        String type;
        for (char c : s.toCharArray()) {
            if (Character.isDigit(c)) type = "digit";
            else if (c == '+' || c == '-') type = "sign";
            else if (c == 'e' || c == 'E') type = "e";
            else if (c == ' ') type = " ";
            else if (c == '.') type = ".";
            else type = "?";
            if (!status[curState].containsKey(type)) return false;
            curState = (int)status[curState].get(type);
        }
        return curState == 2 || curState == 3 || curState == 7 || curState == 8;
    }
}