数字字符串格式化 | 豆包MarsCode AI刷题

123 阅读6分钟

题目背景

问题描述

小M在工作时遇到了一个问题,他需要将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式,并且保留小数部分。小M还发现,有时候输入的数字字符串前面会有无用的 0,这些也需要精简掉。请你帮助小M编写程序,完成这个任务。 这道题目的核心目标是将一个数字字符串按照千分位的格式进行转换,并保留小数部分。数字字符串可能包含无效的前导零,而小数部分的处理则要求保留原样。

样例

输入:s = "0000123456789.99"
输出:'123,456,789.99'

具体任务包括:

  1. 去除前导零:输入的数字可能包含前导零,需要去除。若整个字符串仅为零,返回 "0.0"。
  2. 整数部分千分位格式化:整数部分需要按照每三位加逗号的规则进行格式化(千分位)。
  3. 保留小数部分:如果存在小数部分,则需直接拼接到结果中,并保持其原样。

这个问题实际上是对字符串的格式化操作,涵盖了数字的字符处理、分割、拼接、格式化等多项内容。

代码实现思路

1. 去除前导零

首先,我们需要去除字符串中的前导零。数字字符串可能有多个前导零,需要从左到右查找第一个非零字符的位置。若整个字符串全是零(即长度去除前导零后为空),则返回 "0.0"。

代码实现

javaCopy Code
int first = 0;
while (first < len && s.charAt(first) == '0') {
    first++;
}

这段代码会遍历字符串,直到找到第一个非零字符的位置。如果没有找到,first 会等于字符串的长度,表示字符串中没有有效的数字。

2. 分割整数和小数部分

一旦去除了前导零,我们需要将整数部分和小数部分分开。可以使用 split 方法,通过小数点来分割字符串。如果没有小数点,则表示没有小数部分。

代码实现

String[] str1 = str.split("\\.");
String integerPart = str1[0];
String decimalPart = (str1.length > 1) ? str1[1] : "";

此处使用 split("\\.") 将字符串按小数点分成两部分。注意,split 方法中的 双斜杠 \\. 用于转义,因为在正则表达式中,点号 . 是一个特殊字符,表示任意字符。

3. 整数部分千分位处理

整数部分需要按照千分位进行格式化,每三位插入一个逗号。为了方便处理,我们从右往左遍历整数部分,统计已经插入的数字个数。每当插入三个数字时,就加上一个逗号。

代码实现

StringBuilder res = new StringBuilder();
int cnt = 0;

for (int i = integerPart.length() - 1; i >= 0; i--) {
    res.append(integerPart.charAt(i));
    cnt++;
    if (cnt == 3 && i != 0) {  // 不是最后一位时插入逗号
        res.append(',');
        cnt = 0;
    }
}
res.reverse();

在这段代码中,StringBuilder 用于拼接字符串,cnt 用来统计每次插入的数字个数。每三位数字后,我们就添加一个逗号,直到遍历完整数部分。

因为我们是从右往左插入字符,所以最后需要使用 reverse 方法将结果反转,使其恢复原来的顺序。

4. 拼接小数部分

如果存在小数部分,就直接拼接到格式化后的整数部分之后。如果没有小数部分,则无需做任何操作。

代码实现

if (!decimalPart.isEmpty()) {
    res.append('.').append(decimalPart);
}

这样就能确保小数部分能够保留,并按照原样拼接到最终结果中。

5. 返回结果

最后,返回格式化后的结果。

完整代码

javaCopy Code
public static String solution(String s) {
    int len = s.length();
    int first = 0;

    // 去掉前导零
    while (first < len && s.charAt(first) == '0') {
        first++;
    }

    // 如果去掉前导零后字符串为空,返回 "0.0"
    if (first == len) {
        return "0.0";
    }

    // 获取去掉前导零后的字符串
    String str = s.substring(first);
    
    // 分割整数部分和小数部分
    String[] str1 = str.split("\\."); // 使用双斜杠转义
    String integerPart = str1[0];
    String decimalPart = (str1.length > 1) ? str1[1] : "";

    StringBuilder res = new StringBuilder(); // 用sb会比较快
    int cnt = 0;

    // 添加千分位逗号
    for (int i = integerPart.length() - 1; i >= 0; i--) {
        res.append(integerPart.charAt(i));
        cnt++;
        if (cnt == 3 && i != 0) { // 只在不是最后一位时添加逗号
            res.append(',');
            cnt = 0;
        }
    }

    // 反转字符串
    res.reverse();

    // 拼接小数部分
    if (!decimalPart.isEmpty()) {
        res.append('.').append(decimalPart);
    }

    return res.toString();
}

核心概念与技术

  1. 去除前导零:这是字符串处理中的基础操作,去除前导零有助于避免不必要的格式问题。例如,"000123.45" 应该转换为 "123.45"
  2. 字符串分割与合并split() 用于将字符串按指定分隔符拆分成多个部分,而 StringBuilder 则用于高效地拼接字符串。
  3. 千分位格式化:通过反向遍历和插入逗号,可以将整数部分按每三位插入一个逗号,避免了复杂的字符串插入问题。
  4. 小数部分处理:小数部分的处理较为简单,只需判断是否存在并直接拼接。

性能优化

  1. 时间复杂度:整个处理过程主要分为两部分:

    • 去除前导零:O(n),其中 n 为字符串长度。
    • 处理整数部分的千分位格式化:O(n),因为我们需要遍历整数部分每个字符一次。

    因此,整个算法的时间复杂度为 O(n),是线性时间复杂度,适合处理较长的数字字符串。

  2. 空间复杂度:主要由 StringBuilder 和拆分后的数组占用空间,因此空间复杂度为 O(n),其中 n 为字符串的长度。

常见问题与处理

  1. 小数点后的零:例如 "000123.0045",应当保留小数部分的零。
  2. 长数字的处理:比如 "1000000.123456" 需要按照千分位格式化。

总结与建议

这道题目不仅考察了字符串操作技巧,还涉及到数字格式化的细节,处理了很多常见的边界情况,如前导零和小数部分的处理。掌握这种题型后,可以帮助提升对字符串和数字的理解,尤其是在需要格式化数据时。

如果想进一步提高,可以尝试以下几点:

  • 通过处理不同大小的数字,优化性能。
  • 增加对负数的处理,确保负号正确放置。
  • 练习更复杂的数字格式化题目,如货币格式化、科学计数法等。

通过不断练习类似题目,可以更好地掌握字符串处理与格式化的技巧,提升编程水平。