替换数字:

给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。

例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。

对于输入字符串 "a5b",函数应该将其转换为 "anumberb"

输入:一个字符串 s,s 仅包含小写字母和数字字符。

输出:打印一个新的字符串,其中每个数字字符都被替换为了number

样例输入:a1b2c3

样例输出:anumberbnumbercnumber

数据范围:1 <= s.length < 10000。

STL中的resize()函数

resize()适用于STL中大部分容器,用于改变STL容器的大小。如果重新指定的大小小于原大小(缩小容器),则会从后端删除元素;如果大于原大小(扩大容器),则会在后端添加元素。

resize()的时间复杂度取决于容器类型和具体操作。复杂度视情况为O(n)或O(k),无论哪种都不算大,它是本题一种解法的基础

如何不使用额外的空间得到目标字符串?

如何判断某个字符是数字?

不使用额外空间,意味着只能操作原始字符串本身,显然第一步就是要给原字符串扩容,于是就要用到上面介绍的resize()。那么具体扩容到多少呢?

这取决于字符串s中有多少数字,于是我们需要一个能判断字符是否是数字的函数:

bool isNum(char ch){
    if(ch <= '9' && ch >= '0'){
        return true;
    }
    return false;
}

各个字符的ASCII码不必记住,大部分时候只需要像上面一样借用字符常量来表示即可

如果实在需要判断,大部分时候现场测试一下也能知道答案

扩容操作:

字符串的数字个数只需要遍历一边即可得到,随后将其扩容至对应大小即可:

string s; cin >> s;
int count = 0;//统计数字的个数
for(int i = 0; i < s.size(); i++){
    if(isNum(s[i])){
        count++;
    }
}
s.resize(s.size() + count * 5) //此处乘5是因为number占6个元素,再减去原来的数字所占空间

从后往前的巧妙遍历:

对于字符串a5b而言:

如此扩容后,我们自然想到只需要遍历一遍,将数字替换为number就好了。

问题是如果用正常的顺序遍历,为了不覆盖掉尚未遍历的部分,我们需要在每次数字替换时将后面的元素全部后移6位。这让复杂度瞬间来到O(n2)。如何规避呢?

从后往前遍历是解决类似问题很常见的方法,这个手法是收录这道题最主要的原因:

我们可以从扩容前的字符串尾部出发遍历,同时在新尾部设置一个指针now,用于指示当前最新的空位。如果检测到字母则直接放在s[now],如果是数字则在s[now]到s[now-5]位置分别赋值number六个字符:

这样就非常巧妙地规避了覆盖问题,这是个很重要的基础手法,一定要牢记。

下面是完整代码示例:

#include<bits/stdc++.h>
using namespace std;

//判断是否字符为数字
bool isNum(char ch) {
    if (ch <= '9' && ch >= '0') {
        return true;
    }
    return false;
}

int main() {
    string s; cin >> s; int n = s.size();
    int count = 0;//统计数字的个数
    for (int i = 0; i < s.size(); i++) {
        if (isNum(s[i])) {
            count++;
        }
    }
    s.resize(n + count * 5); //此处乘5是因为number占6个元素,再减去原来的数字所占空间
    int oi = n - 1; int ni = s.size() - 1; //分别为新老字符串的遍历
    
    //老指针遍历完0时结束
    while (oi >= 0) {
        if (isNum(s[oi])) {//是数字,在尾部赋值"number"
            s[ni--] = 'r';
            s[ni--] = 'e';
            s[ni--] = 'b';
            s[ni--] = 'm';
            s[ni--] = 'u';
            s[ni--] = 'n';
        } else {//是字母,在尾部赋值对应字符
            s[ni--] = s[oi];
        }
        oi--;
    }
    cout << s;
    return 0;
}

ACM模式中巧用getchar()的解决方案

由于ACM模式中有输入输出过程,因此可以直接利用getchar()逐个字符读入,根据情况输出即可。直接变成送分题:

#include<bits/stdc++.h>
using namespace std;
//判断是否字符为数字
bool isNum(char ch) {
    if (ch <= '9' && ch >= '0') {
        return true;
    }
    return false;
}
int main() {
    //读入第一个字符
    char ch = getchar();
    //读到换行符或文件末尾时结束
    while(ch != '\n' && ch != EOF){
        if(isNum(ch)){
            cout << "number";
        }else{
            cout << ch;
        }   
        ch = getchar();
    }

    return 0;
}