题目:
Description
Input
Output
Sample Input
12 4873279 ITS-EASY 888-4567 3-10-10-10 888-GLOP TUT-GLOP 967-11-11 310-GINO F101010 888-1200 -4-8-7-3-2-7-9- 487-3279
Sample Output
310-1010 2 487-3279 4 888-4567 3
思路:
题目很简单,将输入的电话号码转成字母,并且找出重复出现的电话号码和重复次数。主要就在于大数据量的处理问题。注意题目的时间限制为2000ms,我下面的代码AC的时间是1375ms,真是险过啊。。。看来自己的姿势水平还是不行啊
代码:(memory:5200k, time 1375ms)
#include<iostream> #include<map> #include<string> using namespace std; int main(){ int num; map<string,int> phones; int mapping[26]={2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,-1,7,7,8,8,8,9,9,9,-1}; map<string, int>::iterator iter; cin>>num; for(int i=0;i<num;i++){ string phone=""; string a; cin>>a; for(unsigned int i=0;i<a.length();i++){ if(a[i]>='A'&&a[i]<='Z'){ phone+=mapping[a[i]-'A']+'0'; } else if(a[i]>='0'&&a[i]<='9'){ phone+=a[i]; } } phones[phone]++;//如果map中没有该key的话,value会自动初始化;如果有该key的话,则改变value值 //此外,因为Map中的元素是自动按key升序排序,所以我就不要再排一次序啦,好省心!(也因为这样,所以不能对map用sort函数。) } bool hasDuplicate=false; for ( iter = phones.begin( ); iter != phones.end( ); iter++ ){ int existNum=iter ->second; if(existNum>1){ hasDuplicate=true; string pho=iter->first; for(int i=0;i<3;i++) cout<<pho[i]; cout<<'-'; for(int i=3;i<7;i++) cout<<pho[i]; cout <<" "<<existNum<<endl; } } if(hasDuplicate==false) cout<<"No duplicates."<<endl; return 0; }
这样的代码(memory 5200k time 1375ms)时间还是不行啊,究竟问题出在哪里了呢?我就去找别人的代码来研究。
被我找到一个(3476k 610ms)的:
#include <iostream> #include <cstdio> #include <cstring> #include <set> #include <map> using namespace std; int num[] = { 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 0, 7, 7, 8, 8, 8, 9, 9, 9 }; map<int, int> s; char buf[128]; int main() { int t; scanf("%d", &t); bool flag = false; for(int i = 0; i < t; i++) { scanf("%s", &buf); int c = 0; for(int j = 0; buf[j]; j++) { if(isdigit(buf[j])) c = c * 10 + buf[j] - '0'; else if(isalpha(buf[j])) c = c * 10 + num[ buf[j] - 'A' ]; } s[c]++; } for(map<int, int>::iterator it = s.begin(); it != s.end(); it++) if(it->second > 1) { flag = true; printf("%03d-%04d %d\n", it->first / 10000, it->first % 10000, it->second); } if(!flag) puts("No duplicates."); return 0; }乍一看这人跟我的思路一样啊,代码也差不多。但是还是有区别的:
1.在map中,我将电话号码存成string,他是用int存储的,考虑到int的最大范围能有10个整数,所以存储7个整数是绰绰有余的。大概在map中,对于int的映射比对于string的映射要快吧。
2.在输出时,他用了printf("%03d-%04d %d\n", it->first / 10000, it->first % 10000, it->second); 这个技巧,输出7位整数的前3位和后4位,好巧妙啊!
我查了一下%03d这样的用法:
%3d--说明输出数据按三个长度的宽度显示,如果要输出的长度大于3时会忽略此时的域宽,以正常显示数据(就是把所有的数字都显示出来,域宽不起作用)。如果不足3位的话,会右对齐,在数据左边以空格补全的,使整个数据以三个长度的域宽显示。
%-3d--左对齐,右边填充空格。%-3d表示以整型十进制格式输出,宽度为3,负值表示左对齐,不足三位在右边补空格。
%03d---一种左边补0 的等宽格式,比如数字12,%03d出来就是: 012。 0(数字零)代表的是不足n位长度的左补齐0。
接下来看一个不用map的解法,使用了qsort。该解法 memory 644k, time 735ms。
//Memory Time //644K 672MS /*Qsort*/ #include<iostream> #include<algorithm> #include<iomanip> using namespace std; void initial(int* ctoi) //把字符ch转换为其在手机上对应的数字键 { for(int i=0;i<=9;i++) ctoi[i+'0']=i; ctoi['A'] = ctoi['B'] = ctoi['C'] = 2; ctoi['D'] = ctoi['E'] = ctoi['F'] = 3; ctoi['G'] = ctoi['H'] = ctoi['I'] = 4; ctoi['J'] = ctoi['K'] = ctoi['L'] = 5; ctoi['M'] = ctoi['N'] = ctoi['O'] = 6; ctoi['P'] = ctoi['R'] = ctoi['S'] = 7; ctoi['T'] = ctoi['U'] = ctoi['V'] = 8; ctoi['W'] = ctoi['X'] = ctoi['Y'] = 9; return; } int main(int i) { int ctoi['Z'+1]; initial(ctoi); int n; //号码数 while(cin>>n) { /*Initial*/ int* sort_out=new int[n]; //按字典序存放待输出的电话号码 /*Input*/ for(i=0;i<n;i++) { int x=0; char s[20]; cin>>s; for(int j=0;s[j]!='\0';j++) { if(s[j]=='-' || s[j]=='Q' || s[j]=='Z') continue; x=x*10+ctoi[ s[j] ]; } sort_out[i]=x; } /*Sort & Output*/ sort(sort_out,sort_out+n); bool flag=true; //标记是否所有号码都是唯一的 for(i=0;i<n;)//注意哦,这里没有i++!因为下面已经把i增加了。 { int time=0; //ort_out[i]出现的次数 int k=sort_out[i]; bool sign=false; //标记k出现次数是否大于2 while(k==sort_out[i] && i<n) { time++; i++; if(time==2) { flag=false; sign=true; } } if(sign) { cout<<setfill('0')<<setw(3)<<k/10000; cout<<'-'; cout<<setfill('0')<<setw(4)<<k%10000; cout<<' '<<time<<endl; } } if(flag) cout<<"No duplicates."<<endl; delete sort_out; } return 0; }他是先将所有的号码转成数字串,存在sort_out数组里,接着用#include<algorithm>里的sort(...,...)函数来排好序,之后在这个排好序的数组里找重复的元素。找的时候很巧妙,一遍就过掉了,发现重复的就打印。
研究了一下algorithm中的sort方法:
STL里面有个sort函数,可以直接对数组排序,复杂度为n*log2(n)。注意:缺省是升序排序。sort中一个改变排序顺序的例子如下(降序):
#include<iostream> #include<algorithm> using namespace std; bool cmp (const int a, const int b) { return a > b; } int main() { int data[5]; for(int i = 0; i < 5; i++) cin >> data[i]; sort(data, data + 5, cmp); return 0; }如需要对数组t的第0到len-1的元素排序,就写sort(t,t+len);对向量v排序也差不多,sort(v.begin(),v.end());排序的数据类型不局限于整数,只要是定义了小于运算的类型都可以,比如字符串类string。
接下来介绍一个典型的以空间换时间的解法。(memory:49296K , time:469ms)
#include<cstdio> #include<cstring> #include<string> #include<map> #include<set> #include<queue> #include<algorithm> using namespace std; int m[256]; int vd[10000000]; bool vs[10000000]; priority_queue< int > q; int main() { m['0']=0; m['1']=1; m['2']=2;m['A']=2;m['B']=2;m['C']=2; m['3']=3;m['D']=3;m['E']=3;m['F']=3; m['4']=4;m['G']=4;m['H']=4;m['I']=4; m['5']=5;m['J']=5;m['K']=5;m['L']=5; m['6']=6;m['M']=6;m['N']=6;m['O']=6; m['7']=7;m['P']=7;m['R']=7;m['S']=7; m['8']=8;m['T']=8;m['U']=8;m['V']=8; m['9']=9;m['W']=9;m['X']=9;m['Y']=9; int n,len; char ss[100],s[100]; scanf("%d",&n); getchar(); for(int i=0;i<n;i++) { scanf("%s",s); getchar(); len=strlen(s); int num=0; for(int j=0;j<len;j++) { if(s[j]=='-'||s[j]=='Q'||s[j]=='Z') continue; else { num=num*10+m[s[j]]; } } vd[num]++; //vd记录出现次数 if(!vs[num]&&vd[num]>=2) //vs数组用来判断该数串有没有被存入q q.push(-num),vs[num]=1; } if(q.empty()) printf("No duplicates.\n"); while(q.empty()==0) { int tem=-q.top(); q.pop(); sprintf(ss,"%07d",tem); for(int j=0;j<7;j++) if(j==2) printf("%c-",ss[j]); else printf("%c",ss[j]); printf(" %d\n",vd[tem]); } return 0; }
看到vd[10000000];我就惊呆了,因为是7位整数,所以使用了10000000(后面7个0),将数组的index=号码数串,value放置出现次数。果然是以空间换时间啊,这样的话每次赋值到数组的某个index上,肯定比map要快啊!使用了优先级队列,存储重复的数串。因为普通的优先级队列按从大到小排序,所以作者用了一个小技巧,压入的是(-1)*真实的数串。然后拿出来的时候再*(-1)一遍,太机智了。
还用到了sprintf(),我又去查了一下:(c++烂伤不起啊T_T)
sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf 在大多数场合可以替代itoa。
如:
//把整数123 打印成一个字符串保存在s 中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"