题目描述
给定一个只包含小写字母的字符串,请你找到第一个仅出现一次的字符。如果没有,输出
no
。输入格式
一个字符串,长度小于 1100。
输出格式
输出第一个仅出现一次的字符,若没有则输出
no
。输入输出样例
输入 #1复制
abc输出 #1复制
c输入 #2复制
aabbcc输出 #2复制
no
方法一:
二维数组+桶计数法
这是我最原始的想法,比较直接,但稍显复杂:
1.对输入的字符串的每个字符进行分析,分别记录每个字符首次出现的顺序和出现的次数,分别存储在二维数组b[ ][ ]中;
2.然后遍历二维数组b[ ][ ],将出现1次以上的字符所对应的次数赋值为0,只剩下出现一次的字符;
3.然后利用min先记录其中一个只出现一次的字符,然后进行循环查找,找到最先出现的字符的编号(最小编号)并赋值给min;
4.最后利用循环查找,找到min所对应的编号的编号(0-25取一值,来代表对应字符),再输出即可
代码如下:
#include<iostream>
using namespace std;
int main(void)
{
char a[1101];
int b[26][2] = {0,0};
cin >> a;
int j = 1;
int n = sizeof(a) / sizeof(a[0]);
for (int i = 1; i <= n; i++)
{
b[a[i-1]-97][1]++;
if (b[a[i - 1] - 97][0] == 0) {
b[a[i - 1] - 97][0] = j++;
}
}
int min=26;
for (int i = 0; i < 26; i++)
{
if (b[i][1] == 1)
{
min = b[i][0];
}
else {
b[i][0] = 0;
b[i][1] = 0;
}
}
for (int i = 0; i < 26; i++)
{
if (b[i][0] < min && b[i][0]!=0) min = b[i][0];
else continue;
}
for (int i = 0; i < 26; i++)
{
if (min == b[i][0]) {
cout << (char)(i + 97);
exit(EXIT_SUCCESS);
}
}
cout << "no";
return 0;
}
方法二:
一维数组+桶计数法
由于方法一过于复杂,所以我想着能不能用更加简单的方法来完成这道题。方法二非常简单,直接查找,没有任何复杂的操作。
1.我们枚举输入的字符串
str
每个字符(0到 n−1),如果出现就标记visit[str[i] - 'a']
为true
,然后如果枚举到一个字符并且他的visit
为true
(被访问过),就把他的flag
标记为true
(标记为不可以)2.然后再次枚举每个字符,如果这个字符的
flag
为false
(只被访问过 1 次)那就直接输出,然后
return 0
(这样就保证了是第一个)如果没有
flag
为false
的,就输出no
。
代码如下:
#include<iostream>
using namespace std;
bool visit[30];
bool flag[30];
int main()
{
string str;
cin >> str;
for(int i = 0; i < str.size(); i++)
{
if(visit[str[i] - 'a'])
{
flag[str[i] - 'a'] = true;
}
visit[str[i] - 'a'] = true;
}
for(int i = 0; i < str.size(); i++)
{
if(!flag[str[i] - 'a'])
{
cout << str[i];
return 0;
}
}
cout << "no";
return 0;
}
这个方法的好处在于规避了复杂的计算方法,用更为直接的手段进行解题,整体来看没有任何多余的代码,强烈推荐!
方法三:
map+桶计数法
题目中给的是字符串,怎么用整形表示?我们想一想,有哪个数据结构可以实现不同数据类型的映射?答案是: STL 库中的 map !
介于大家可能并不是十分了解 map ,这里简单介绍一下:我们可以把 map 看作一个可以存储任何数据形式的动态数组(也就是下标不做限制的数组啦),它的定义方式为:
map <存储进去的类型,用来表示数据的类型> 名称;
举个例子,在本题中我们可以建立一个 char 类型与 int 类型相互映射(照应)的 map 来进行桶排序,即定义过程为
map <char,int> a;
最后是闪亮的代码啦!
#include <map> //使用 map 要调用 map 库
#include <iostream>
#include <string>
using namespace std;
map <char,int> a;
string s;
int main()
{
cin>>s;
for(int i = 0; i <s.size(); i++) //注意字符串从 0 开始
{
a[s[i]]++;//将所有字符出现的次数都算进去
}
for(int i = 0; i <s.size(); i++)
{
if(a[s[i]] == 1)//第一个为1的字符必定是目标字符
{
cout<<s[i];
return 0;
}
}
cout<<"no";
return 0;
}
方法四:
桶计数法:
此方法和方法3有异曲同工之妙,先按照字符的出现顺序遍历,将出现的次数和出现的顺序绑定起来,利用桶记录次数;然后直接从第一个字符开始遍历,判断是否满足条件即可;
可能会有人问:最先遍历出的符合要求字符一定是目标字符吗?答案是肯定的。
因为我们已经利用桶将字符的出现顺序和出现的次数绑定起来了,再次遍历的时候每个字符出现的次数都是已知的,而出现的顺序也是不变的,所以相当于给我们给定一行特定顺序的字符,例如:abcdefghijklmnopqrstuvwxyzabc;现在我们已知第一个出现一次的字符是'd',然后从'a'开始遍历,判断每个字符出现的次数,a出现两次,不符合条件;继续判断b...;直到找到符合条件的字符为止,找到后立刻输出并返回;否则输出no;
#include<bits/stdc++.h>
using namespace std;
int a[30];
int main()
{
string st;
cin>>st;
for(int i=0;i<st.size();i++)//这个字母出现了几次
{
a[st[i]-'a']++;//转化成较小数字
}
for(int i=0;i<st.size();i++)
{
if(a[st[i]-'a']==1)//如果出现了一次
{
cout<<st[i];
return 0;//直接退出程序
}
}
cout<<"no";
return 0;
}
我一直认为刷题不仅仅是刷题,更重要的是还是要学会思考,解决问题的方式有很多,但如何寻找适合自己的方法尤为重要。如果你有好的方法和建议,请在评论区留言,我们一起学习、进步!