字符串转换整数

字符串转换整数(atoi)Java 题解:状态解析与边界截断

实现 myAtoi(String s),依次处理前导空格、正负号、连续数字和非法字符,并在结果超出 32 位有符号整数范围时返回对应边界。本文提供不依赖 64 位整数的 Java 实现、过程图解、完整测试及常见错误分析。

一、题目要求

字符串转整数需要严格按照以下顺序处理:

  1. 丢弃前导普通空格 ' '
  2. 读取至多一个正负号,默认正数;
  3. 连续读取数字,遇到第一个非数字字符就停止;
  4. 没有读到任何数字时返回 0
  5. 小于 Integer.MIN_VALUE 时返回最小值,大于 Integer.MAX_VALUE 时返回最大值。

32 位范围为:

text 复制代码
[-2147483648, 2147483647]

二、几个容易误解的输入

text 复制代码
"42"             -> 42
"   -042"        -> -42
"1337c0d3"       -> 1337
"0-1"            -> 0
"words and 987"  -> 0

0-1 不能解释成 -1,因为数字读取已经从 0 开始,遇到 - 后必须停止。words and 987 在第一个字符 w 处就停止,因此返回 0

三、核心思路

使用下标 index 从左向右扫描字符串:

  • 第一阶段跳过空格;
  • 第二阶段读取一个可选符号;
  • 第三阶段只要当前字符是数字,就将它追加到结果;
  • 遇到非数字或字符串末尾时结束。

追加一位数字的公式是:

java 复制代码
value = value * 10 + digit;

但不能计算完成后再判断溢出,因为 int 可能已经溢出。必须在乘以 10 之前预判。

四、不使用 long 的边界技巧

正数最大绝对值为 2147483647,负数允许的绝对值为 2147483648。后者无法用正 int 保存。

一个稳妥技巧是:始终用负数累计结果 。因为 int 的负数范围比正数多一个单位,-2147483648 可以被 int 直接保存。

java 复制代码
result = result * 10 - digit;

正数输入使用限制 -2147483647,负数输入使用限制 -2147483648。在追加前检查:

java 复制代码
if (result < limit / 10
        || (result == limit / 10 && -digit < limit % 10)) {
    return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}

整个过程只使用 int

五、可直接使用的 Java 答案

java 复制代码
class Solution {
    public int myAtoi(String s) {
        int length = s.length();
        int index = 0;

        while (index < length && s.charAt(index) == ' ') {
            index++;
        }

        int sign = 1;
        if (index < length
                && (s.charAt(index) == '+' || s.charAt(index) == '-')) {
            if (s.charAt(index) == '-') {
                sign = -1;
            }
            index++;
        }

        int limit = sign == 1 ? -Integer.MAX_VALUE : Integer.MIN_VALUE;
        int result = 0;

        while (index < length) {
            char current = s.charAt(index);
            if (current < '0' || current > '9') {
                break;
            }

            int digit = current - '0';

            if (result < limit / 10
                    || (result == limit / 10 && -digit < limit % 10)) {
                return sign == 1
                        ? Integer.MAX_VALUE
                        : Integer.MIN_VALUE;
            }

            result = result * 10 - digit;
            index++;
        }

        return sign == 1 ? -result : result;
    }
}

六、为什么采用负数累计

Java 的 int 范围并不对称:

text 复制代码
最大值: 2147483647
最小值:-2147483648

如果使用正数累计,负数最小值的绝对值 2147483648 无法存入 int。使用负数累计后,正负两个边界都能在 int 中表达,最后只在正数情况下取反。

七、示例过程:" -042"

阶段 内容 状态
跳过空格 跳过 3 个空格 index 指向 -
读取符号 读取 - sign = -1
读取数字 读取 042 result = -42
返回 负数无需取反 -42

前导零不需要单独删除。将 0 追加到当前结果不会改变数值,所以它会自然被忽略。

八、停止规则

只有紧跟在可选符号之后的连续数字才参与转换:

text 复制代码
"1337c0d3" -> 读取 1337,遇到 c 停止
"+-12"     -> 读取 + 后遇到 -,没有数字,返回 0
"  +  12"  -> 符号后遇到空格,没有数字,返回 0

注意:只丢弃最前面的普通空格。读取符号后,不会再次跳过空格。

九、复杂度分析

设字符串长度为 n

  • 时间复杂度:O(n)
  • 额外空间复杂度:O(1)

每个字符最多访问一次,不创建与输入长度相关的额外容器。

十、完整可运行代码

java 复制代码
public class StringToIntegerTest {

    public static int myAtoi(String s) {
        int index = 0;
        int length = s.length();

        while (index < length && s.charAt(index) == ' ') {
            index++;
        }

        int sign = 1;
        if (index < length
                && (s.charAt(index) == '+' || s.charAt(index) == '-')) {
            sign = s.charAt(index) == '-' ? -1 : 1;
            index++;
        }

        int limit = sign == 1 ? -Integer.MAX_VALUE : Integer.MIN_VALUE;
        int result = 0;

        while (index < length) {
            char current = s.charAt(index);
            if (current < '0' || current > '9') {
                break;
            }

            int digit = current - '0';
            if (result < limit / 10
                    || (result == limit / 10 && -digit < limit % 10)) {
                return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }

            result = result * 10 - digit;
            index++;
        }

        return sign == 1 ? -result : result;
    }

    private static void runTest(int number, String input, int expected) {
        int actual = myAtoi(input);
        System.out.println("测试 " + number + ": "
                + (actual == expected ? "PASS" : "FAIL")
                + " | input=\"" + input + "\""
                + " | actual=" + actual);
    }

    public static void main(String[] args) {
        runTest(1, "42", 42);
        runTest(2, "   -042", -42);
        runTest(3, "1337c0d3", 1337);
        runTest(4, "0-1", 0);
        runTest(5, "words and 987", 0);
        runTest(6, "2147483648", Integer.MAX_VALUE);
        runTest(7, "-2147483649", Integer.MIN_VALUE);
        runTest(8, "+-12", 0);
        runTest(9, "00000123", 123);
    }
}

十一、测试结果

text 复制代码
测试 1:PASS | "42" -> 42
测试 2:PASS | "   -042" -> -42
测试 3:PASS | "1337c0d3" -> 1337
测试 4:PASS | "0-1" -> 0
测试 5:PASS | "words and 987" -> 0
测试 6:PASS | "2147483648" -> 2147483647
测试 7:PASS | "-2147483649" -> -2147483648
测试 8:PASS | "+-12" -> 0
测试 9:PASS | "00000123" -> 123

十二、常见错误

1. 使用 trim()

trim() 会创建新字符串,而且可能掩盖"只跳过前导普通空格"的读取规则。用下标扫描更直接。

2. 符号后再次跳过空格

"+ 12" 应返回 0,符号与数字之间不能插入空格。

3. 先计算再判断溢出

result * 10 + digit 可能先溢出。必须在追加数字前比较边界。

4. 使用 long 暂存

虽然写法简单,但负数累计可以完全在 int 范围内安全实现,更符合边界算法的训练目的。

5. 继续寻找后面的数字

遇到第一个非数字字符就必须停止,不能跳过它继续读取后面的数字。

十三、总结

字符串转换整数可以看成一个简单的顺序解析器:

  1. 跳过前导空格;
  2. 读取至多一个符号;
  3. 连续读取数字;
  4. 遇到其他字符立即停止;
  5. 在追加数字前预判边界;
  6. 越界时返回对应的 32 位极值。

使用负数累计可以在不依赖 64 位整数的情况下,统一处理 2147483647-2147483648 两个不对称边界。

更多技术实践与案例,可在 📗【姚前述】中查阅。

相关推荐
会编程的吕洞宾2 小时前
DeepAgents In Action学习(Second)
android·java·学习
梦梦代码精2 小时前
基于UniApp+Vue3+ThinkPHP 8,这套知识付费系统的架构设计有点东西
java·低代码·docker·uni-app·开源·php
-凌凌漆-2 小时前
【freertos】Task创建(v2)
java·开发语言·算法
牛油果子哥q2 小时前
C++内存模型与深浅拷贝万字详解:栈堆静态内存布局、深浅拷贝底层差异、内存泄漏根治、拷贝崩溃踩坑、手写深拷贝实战
java·开发语言·c++
带多刺的玫瑰4 小时前
Leecode#4刷题之寻找两个正序数组的中位数
java·前端·算法
阿无,5 小时前
布隆过滤器
java·算法·哈希算法
Pocker_Spades_A5 小时前
飞算JavaAI 48分钟能做完Java全栈项目吗?
java·springboot·#ai编程·#飞算javaai·#java代码生成·#aicoding模型·#java开发
步行cgn5 小时前
MyBatis 一对多关联映射详解
java·后端
用户3126874877205 小时前
线程池到底怎么调?从源码看懂 ThreadPoolExecutor 的 7 个参数
java