问题引入
最近在解决AHNU在线测试http://172.16.94.19:8080/JudgeOnline/showproblem?problem_id=1496时,遇到了麻烦,问题描述如下:
Description
读取一行文本,输出相同的文本,但将所有整数中的数字替换成’x’(长度不限)。
Input
一行文本。
Output
替换后的一行文本。
Sample Input
My userID is john17 and my 4 digit pin is 12345 which is secret.
Sample Output
My userID is john17 and my x digit pin is xxxxx which is secret.
初步解决步骤如下:
①将输入的文本text按照空格为分隔符,将每个单词存到字符串数组word中(单词总数可由text字符串中空格数确定);
②对字符串数组word的每一个元素word[i]字符串进行判断,若字符串word[i]中全由数字组成,则将该串的每一个字符替换为'x';
③构造循环,依次输出字符串数组的每一个元素,实现数字替换。
基于以上想法,构造以下函数,分别为:字符串切割函数(实现步骤①)、字符串转换函数(实现步骤②)
字符串切割函数
void Split(char *text,char **word)
{
int i=0;
int k=0;//当前已完成单词数
int j=0;
while(i<strlen(text))
{
j=0;
while(text[i]!=' '&&text[i]!='\0')
{
word[k][j]=text[i];
j++;
i++;
}
word[k][j]='\0';
//printf("%s ",word[k]);
//printf("k:%d i:%d j:%d\n",k,i,j);
k++;
i++;
}
}
字符串转换函数
void Convert(char **word,int num)//若字符串s中全为数字,则将所有数字转化为'x'
{
int i,j=0;
int count=0;
int R[256]={0};
/*********************初始化数组R**********************/
for(i=48;i<=58;i++)
R[i]=1;//数字位置置为1
/*****************************************************/
for(i=0;i<num;i++)
{
j=0;
count=0;
while(j<strlen(word[i]))
{
count+=R[word[i][j]];
j++;
}
//printf("%d ",count);
if(count==strlen(word[i]))//数字字符数等于字符串长度
{
j=0;
while(j<strlen(word[i]))
{
word[i][j]='x';
j++;
}
}
}
}
主函数
int main()
{
char text[300000];//要求输入长度无限
char **word;
int num=0,i;
gets(text);
num=WordCount(text);
word=(char**)malloc(num*sizeof(char*));
for(i=0;i<num;i++)
word[i]=(char*)malloc(50*sizeof(char));//为每个单词分配内存空间
Split(text,word);
Convert(word,num);
for(i=0;i<num;i++)
printf("%s ",word[i]);//输出测试
for(i=0;i<num;i++)
free(word[i]);//释放每个单词内存空间
free(word);
//printf("%d",num);
return 0;
}
实际运行时,对于长度在可接受范围内的字符串,运行效果良好。但对于测试数据5,输入的数据为一长度近300000的字符串,此时程序运行缓慢,近20s,提交时出现了Time Limit Exceed 的错误。通过分析,主要时间花费在字符串切割函数Split()中,因此若基于以上解题步骤,必须找到一种高效的字符串切割算法。
算法改进
实际上,C语言头文件<string.h>中提供了字符串切割函数strok(),经过测试,该函数的时间复杂度较好,能够满足要求。将其与原字符串切割函数替换后,程序执行时间大大降低,可保持在10s内。Convert()字符串转换函数思想不改,同时将字符串切割体现在主函数里。修改后的Convert()和main()函数如下:
字符串转换函数Convert()
void Convert(char *word)//若字符串s中全为数字,则将所有数字转化为'x'
{
int i,j=0;
int count=0;
int R[256]={0};
/*********************初始化数组R**********************/
for(i=48;i<=58;i++)
R[i]=1;//数字位置置为1
/*****************************************************/
j=0;
count=0;
while(j<strlen(word))
{
count+=R[word[j]];
j++;
}
if(count==strlen(word))//数字字符数等于字符串长度
{
j=0;
while(j<strlen(word))
{
word[j]='x';
j++;
}
}
}
主函数main()
int main(void)
{
char text[3000000];
gets(text);
char *temp = strtok(text," ");
printf("%s\n",temp);
while(temp)
{
Convert(temp);
printf("%s ",temp);
temp = strtok(NULL," ");
}
return 0;
}
结论
strotok()函数切割字符串效率较高,实际应用中应当掌握该函数的参数以及返回类型,必要时需要明白实现机制。关于strotok()函数我将在下一篇博客中详细介绍。
P.S. 20190807更新:最近重新看这题,发现一个容易理解的新的算法
大致思想是:找到字符串中所有空格出现的位置,之后判断空格之间的字符是否全部是数字,若是,则替换为'x'(实际上也是分割的思想)。这里需要注意-1和字符串长度也要作为空格出现的位置容器中(原因不解释,应该很容易理解)。
#include <iostream>
#include <vector>
#include <cctype>
using namespace std;
void showPos(vector<int> pos);
/*
New solution:
find all blank's position, and judge whether the string between near two blanks only includes digit.
*/
int main()
{
string s;
int i,n=0,k;
int l,r;
int flag;
vector<int> pos;
getline(cin,s);
//cout<<s<<endl;
pos.push_back(-1);//Attention
n++;
for(i=0;i<s.length();i++)
{
if(s.at(i)==' ')
{
pos.push_back(i);
n++;
}
}
pos.push_back(s.length());//Attention
n++;
//showPos(pos);
for(i=0;i<n-1;i++)
{
flag=0;
l=pos[i]+1;
r=pos[i+1]-1;
//cout<<"("<<l<<","<<r<<")"<<endl;
for(k=l;k<=r;k++)
if(!isdigit(s.at(k)))
flag=1;
if(!flag)
for(k=l;k<=r;k++)
s[k]='x';
}
cout<<s<<endl;
return 0;
}
void showPos(vector<int> pos)
{
int i=0;
int n=pos.size();
for(;i<n;i++)
cout<<pos[i]<<" ";
cout<<endl;
}