【LeetCode】65.Valid Number DFA应用

本文深入探讨了使用确定有限自动机(DFA)判断字符串是否为合法数字的方法,对比了两种不同的状态转移策略,分析了边界条件的处理技巧,并提供了一种更简洁高效的实现方案。

一、概述

输入一个字符串,判断其是否是一个合法的数字。

孤儿题目,边界条件极其多,需要不断submit来一个一个找出来,我错了二十多次,简直气死。

之前没用过DFA,DFA还是很好用的,Debug的时候很直观很容易,打补丁就好了。但是打补丁并不是一个好主意,会让代码可读性变得很差,不优美。因此如何设计状态,让状态与条件判断达到一个平衡,是一个技术活。

二、分析

1、我的方法

咱们先来看看,最初我想法很简单,因此状态转移图也很简单。

直到我发现.5时正确的,事情开始不对了:

然后开始疯狂打补丁:

为什么要这么多补丁呢?

主要是因为.5是正确的,3.是正确的,但是.自己是错误的,我原来就认为3.5这种是正确的,前面两者都是错误的——所以才弄错。另外,没有考虑空格也是一个原因——但是空格的状态是不能省略的。

然后呢?没考虑.3.5是错误的,e3e5是错误的,3.e5是正确的,3e3.5是错误的,3e3.是错误的——反正就一大堆错。最后弄出这么个丑陋的代码:

class Solution {
public:
    bool isNumber(string s) {
        bool res=true;
        int flag=0;
        int point=0;
        int e=0;
        for(int i=0;i<s.size();++i)
        {
            if(flag==0)
            {
                if((s[i]=='+'||s[i]=='-')&&i!=s.size()-1)
                    flag=1;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='.'&&i!=s.size()-1)
                {
                    flag=6;
                    point=1;
                }
                else if(s[i]==' '&&i!=s.size()-1)
                    flag=0;
                else
                    return false;
            }
            else if(flag==1)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='.'&&point!=1&&i!=s.size()-1)
                {
                    if(point==0)
                    {
                       flag=4;
                        point=1;
                    }
                    else
                        return false;
                    
                }
                else
                    return false;
            }
            else if(flag==2)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='e'&&i!=s.size()-1)
                {
                    if(e==0)
                    {
                        flag=3;
                        e=1;
                    }
                    else
                        return false;
                    
                }
                else if(s[i]=='.'&&point!=1&&e!=1)
                {
                    if(point==0)
                    {
                       flag=4;
                        point=1;
                    }
                    else
                        return false;
                    
                }
                else if(s[i]==' ')
                    flag=5;
                else
                    return false;
            }
            else if(flag==3)
            {
                if((s[i]=='+'||s[i]=='-')&&i!=s.size()-1)
                    flag=1;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else
                    return false;
            }
            else if(flag==4)
            {
                if(e>0)
                    return false;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]==' ')
                    flag=5;
                else if(s[i]=='e'&&i!=s.size()-1)
                {
                    if(e==0)
                    {
                       flag=3;
                        e=1;
                    }
                    else
                        return false;
                }
                else
                    return false;
            }
            else if(flag==5)
            {
                if(s[i]==' ')
                    flag=5;
                else
                    return false;
            }
            else if(flag==6)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else
                    return false;
            }
        }
        return true;
    }
};

总结起来就是:

+-不能在最后一个;

.可以在最后一个,但是.不能单独出现,.只能有一个,.不能在e后面;

空格只有在首尾出现才是合法的;

e只能有一个,e不能是最后一个。

这些题设中都没说!!!

2、较好的方法

我的方法是先判断当前状态,再判断该位是什么,然后转移状态;较好的方法是先判断该位是什么,再判断当前状态,然后转移状态。其实这本身没什么好不好,关键是人家对状态数和条件判断做了很好的平衡。

该方法首先将首尾的空格去掉,那么遇到空格就可以直接返回false,这样来讲,我的状态就只有6个,而他有八个状态,那么就比我多了两个状态,相应的,他的条件判断就简洁了很多。简而言之,就是用两个状态代替了我的e和point。状态转移如下图:

该方法与图片均来自该网址

仔细观察可知,在状态s3,即e的后面,出现数字的时候,他并没有选择返回s2,而是走向s5,同理,出现+或-的时候,并没有返回s1,而是走向s4。然后,在s6的时候,出现数字,转向s7而不是s2。最后,他指定了四个状态为最后的正确状态。只有循环结束时状态为这四个才是正确的。

PS:s6转向s3仅在.之前有数字的时候才是正确的。下文中的flag就是为了这种情况。

代码如下:

class Solution {
public:
    bool isNumber(string str) {
        int state=0, flag=0; // flag to judge the special case "."
        while(str[0]==' ')  str.erase(0,1);//delete the  prefix whitespace 
        while(str[str.length()-1]==' ') str.erase(str.length()-1, 1);//delete the suffix whitespace
        for(int i=0; i<str.length(); i++){
            if('0'<=str[i] && str[i]<='9'){
                flag=1;
                if(state<=2) state=2;
                else state=(state<=5)?5:7;
            }
            else if('+'==str[i] || '-'==str[i]){
                if(state==0 || state==3) state++;
                else return false;
            }
            else if('.'==str[i]){
                if(state<=2) state=6;
                else return false;
            }
            else if('e'==str[i]){
                if(flag&&(state==2 || state==6 || state==7)) state=3;
                else return false;
            }
            else return false;
        }
        return (state==2 || state==5 || (flag&&state==6) || state==7);
    }
};

三、总结

DFA原理不难,难的是把所有转移情况都想到,同时保证状态数和条件判断的平衡。

虽然debug很方便,但要是不注意,就会需要很多次试错才能AC,在有罚时的情况下是很吃亏的。因此一定要注意全面思考。

### 如何在 VSCode 中安装和配置 LeetCode 插件以及 Node.js 运行环境 #### 安装 LeetCode 插件 在 VSCode 的扩展市场中搜索 `leetcode`,找到官方提供的插件并点击 **Install** 按钮进行安装[^1]。如果已经安装过该插件,则无需重复操作。 #### 下载与安装 Node.js 由于 LeetCode 插件依赖于 Node.js 环境,因此需要下载并安装 Node.js。访问官方网站 https://nodejs.org/en/ 并选择适合当前系统的版本(推荐使用 LTS 版本)。按照向导完成安装流程后,需确认 Node.js 是否成功安装到系统环境中[^2]。 可以通过命令行运行以下代码来验证: ```bash node -v npm -v ``` 上述命令应返回对应的 Node.js 和 npm 的版本号。如果没有正常返回版本信息,则可能未正确配置环境变量。 #### 解决环境路径问题 即使完成了 Node.js 的安装,仍可能出现类似 “LeetCode extension needs Node.js installed in environment path” 或者 “command ‘leetcode.toggleLeetCodeCn’ not found” 的错误提示[^3]。这通常是因为 VSCode 未能识别全局的 Node.js 路径或者本地安装的 nvm 默认版本未被正确加载[^4]。 解决方法如下: 1. 手动指定 Node.js 可执行文件的位置 在 VSCode 设置界面中输入关键词 `leetcode`,定位至选项 **Node Path**,将其值设为实际的 Node.js 安装目录下的 `node.exe` 文件位置。例如:`C:\Program Files\nodejs\node.exe`。 2. 使用 NVM 用户管理工具调整默认版本 如果通过 nvm 工具切换了不同的 Node.js 版本,确保设置了默认使用的版本号。可通过以下指令实现: ```bash nvm alias default <version> ``` 重新启动 VSCode 后测试功能键是否恢复正常工作状态。 --- #### 配置常用刷题语言 最后一步是在 VSCode 设置面板中的 LeetCode 插件部分定义个人习惯采用的主要编程语言作为默认提交方式之一。这样可以减少频繁修改编码风格的时间成本。 --- ### 总结 综上所述,要在 VSCode 上顺利启用 LeetCode 插件及其关联服务,除了基本插件本身外还需额外准备支持性的后台框架——即 Node.js 应用程序引擎;同时针对特定场景下产生的兼容性障碍采取针对性措施加以修正即可达成目标[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值