剑指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
  • 最后也需要跳过空格
  • 最后判断是否合法的条件是:是否到达最后⼀个字符,并且出现过数字
java 复制代码
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 就是⼀个分割线。

正则表达式

直接借助正则表达式进⾏匹配,但是并不太推荐这种解法:

java 复制代码
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种状态,根据输入字符进行状态转移

java 复制代码
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)
相关推荐
凌冰_2 小时前
IDEA2025 搭建Web并部署到Tomcat运行Servlet+Thymeleaf
java·servlet·tomcat
木楚2 小时前
在idea中构建传统ssm框架的步骤和方式
java·ide·intellij-idea
董世昌412 小时前
JavaScript 中 undefined 和 not defined 的区别
java·服务器·javascript
Lisonseekpan2 小时前
Kafka、ActiveMQ、RabbitMQ、RocketMQ对比
java·后端·kafka·rabbitmq·rocketmq·activemq
我是华为OD~HR~栗栗呀2 小时前
(华为od)21届-Python面经
java·前端·c++·python·华为od·华为·面试
夕阳下的一片树叶9132 小时前
后端java遇到的问题
java·开发语言
CodeAmaz2 小时前
RocketMQ整体工作流程_详解
java·rocketmq·rocketmq整体流程
刘一说2 小时前
ES6+核心特性全面浅析
java·前端·es6
大学生资源网2 小时前
基于springboot的农村综合风貌展示平台设计与实现(源码+文档)
java·数据库·spring boot·后端·毕业设计·源码·springboot