今天我们将学习如何在一行句子中寻找(第一次出现的)最长最短单词。本节内容会或多或少地利用到第一讲/第二讲的知识点,需要的同学可以先去看看前面的内容。
一、小试牛刀:
题目描述
输入 1 行句子(不多于 200 个单词,每个单词长度不超过 100),只包含字母、空格、逗号和句号。单词由至少一个连续的字母构成,空格、逗号和句号都是单词间的间隔。
输出第 1 个最长的单词和第 1 个最短单词。输入格式
输入数据:一行句子。输出格式
输出数据:
- 第 1 行,第一个最长的单词。
- 第 2 行,第一个最短的单词。
输入输出样例
输入:
I am a student,i am studying Programming language C in Peking University.
输出:
Programming
I
- 根据题目要求,我们只需要在所有合法的字符串中,输出第一个出现的最长单词和最短单词。
1. 方法一:
- 我们可以对于每个读入的字符串利用
C++
的string
库里的find()
函数查找目标字符','
/'.'
是否存在,并利用substr()
函数来截取所需的字符串(goto
可以解决连续逗号问题),最后将所有合法的字符串存储到vector
容器中。具体代码如下:
#include<iostream>
#include<vector>
#include<string>
using namespace std;
vector<string>str;//存储字符串
vector<int>str_length;//存储字符串长度
string s, s1;
size_t found;//用于find函数
int main(void)
{
int cnt = 0;//字符串个数
while (1)
{
cin >> s;
loop:
found = s.find(',');//在s中查找逗号是否存在
if (found != string::npos)//如果找到了逗号
{
s1 = s.substr(0, found);//0~found截取字符串
str.push_back(s1);//将截取的字符串存入str
str_length.push_back(s1.length());//将截取的字符串长度存入str_length
cnt++;
s = s.substr(found + 1);//将s截取为逗号后面的字符串
goto loop;//为了避免逗号连续出现,所以用goto返回到loop继续判断
}
if (s[s.length() - 1] == '.') {//如果该字符最后一个字符是句号
s1=s.substr(0, s.length() - 1);//截取句号前面的字符串
str.push_back(s1);
str_length.push_back(s1.length());
cnt++;
break;
}
str.push_back(s);
str_length.push_back(s.length());
cnt++;
}
int max = 0;
int min = 0;
for (int i = 0; i < cnt; i++)
{
if(str_length[i]>str_length[max]) max = i;
if(str_length[i]< str_length[min]) min = i;
//cout << str[i] << " ";//print大法
}
cout << str[max] << endl;
cout << str[min];
return 0;
}
- 此方法虽然容易想,但是本身的执行效率并不高,时间复杂度和空间复杂度占用较大。
2. 方法二:
相信大家拿到这题后很快就可以想出来,我们需要发现一个单词,在单词中间记录信息,再对比信息。
- 那么怎样判断一个单词的开始与结束呢?
我们在阅读的时候,每个单词开始前都会有一个标点符号或空格提醒我们这是下一个单词,那么代码也一样,发现一个空格或标点符号,更新数据。
for(int i=0;i<a.length();i++){
if(a[i] == ' ' || a[i] == ',' || a[i] == '.'){
//更新数据;
}
//单词长度++;
}
- 如何更新数据?
可以设置两个变量,一个记录最长单词的结束位置,一个记录最长单词的长度,这样输出时我们就可以用结束位置-长度找到开始位置,并且输出。最小值也一样。
- 具体代码如下:
#include <iostream>
#include <string>
using namespace std;
string a;
int sum = 0, maxx = -100, maxb, minb, minn = 1000000;
int main() {
getline(cin, a);//带空格输入
for (int i = 0; i < a.length(); i++) {
if (a[i] == ' ' || a[i] == ',' || a[i] == '.') {//如果发现标点符号和空格,代表单词结束
if (maxx < sum) {//如果单词长度突破目前发现的最长长度
maxb = i;//把最长单词的结束点更新,i就是结束点
maxx = sum;//把最长单词的长度更新
}
if (minn > sum) {//如果单词长度低于目前发现的最短长度
minb = i;//更新最短单词结束点
minn = sum;//更新最短长度
}
sum = 0;//sum清空,以便下次操作
}
else {
sum++;//如果是字母,长度加一
}
}
for (int i = maxb - maxx/*从开始位置开始输出*/; i < maxb/*到结束位置结束*/; i++) {
cout << a[i];
}
cout << endl;
for (int i = minb - minn; i < minb; i++) {//最短单词同理
cout << a[i];
}
return 0;
}
- 这样做可谓是非常妙,时间复杂度和空间复杂度大大降低,极大地提高了代码的运行效率,并且撇弃了复杂的操作,不仅更易书写,也使读者更易理解。
3. 方法三:
- 每个字符逐个读入,读取到换行或文件结束时退出。先判断是不是分隔符,如果是,就更新最长单词和最短单词,并清空当前单词,否则添加当前字符到当前单词里。最后输出结果就可以了。代码如下:
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int main()
{
string a,b,t;
int la=0,lb=1000;
char ch;
while(ch=getchar())
{
if(ch=='\n'||ch==EOF) break;
if(ch==' '||ch==','||ch=='.')
{
if(t.length()>la)
a=t,la=t.length();
if(t.length()<lb)
b=t,lb=t.length();
t="";
continue;
}
t.push_back(ch);
}
cout<<a<<endl<<b;
return 0;
}
- 其实方法3本身和方法2并没有太大的区别,但相较于方法2来说则更为简便,对于结果的输出不再是利用做差得到,而是直接输出,使得代码进一步优化。
4. 方法四:
- 在这题中,我们需要得出句中最长的单词与最短的单词。因此,我们可以采用
string
字符串 的方式进行扫描。首先是整体的字符串 s ,要使用getline
进行输入,因为 字符串中有空格。然后是单词的字符串 s0 ,同时需要计数(单词长度),再与ans1
,ans2
打擂台得到最大值与最小值。
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int ans1=0,ans2=0x3f3f3f,cnt;
string s,s1,s2,s0;
int main()
{
getline(cin,s);//一整行输入
for(int i=0;i<s.length();i++)
{
if(s[i]==' '||s[i]==','||s[i]=='.')//判定单词末尾(注意中英符号区分)
{
if(cnt>ans1)//最大值打擂台
{
s1=s0;
ans1=cnt;
}
if(cnt<ans2)//最小值打擂台
{
s2=s0;
ans2=cnt;
}
cnt=0;
s0.clear();//清除当前单词
}
else
{
s0+=s[i]; //一般情况(记录单词字母以及其个数)
cnt+=1;
}
}
cout<<s1<<endl<<s2<<endl;//一般输出
return 0;
}
- 此方法和方法2,3类似,不做过多解释。
二、拓展讲解:
1.substr()函数:
C++
的substr()
函数是用于提取字符串的子串的函数。它接受两个参数,第一个参数是起始位置,第二个参数是要提取的子串的长度。函数返回一个新的字符串,包含从起始位置开始的指定长度的字符。
下面是substr()
函数的语法:
string substr (size_t pos, size_t len) const;
pos
:起始位置,表示要提取子串的起始字符的索引。索引从0开始计数。len
:子串的长度,表示要提取的字符数量。
下面是一个示例代码,演示如何使用substr()
函数:
#include <iostream>
#include <string>
int main() {
std::string str = "Hello,World!";
// 提取从索引6开始的子串,长度为5
std::string sub = str.substr(6, 5);
std::cout << sub << std::endl; // 输出 "World"
return 0;
}
在上面的示例中,我们首先定义了一个字符串str
,然后使用substr()
函数提取了从索引6开始的子串,长度为5。提取的子串存储在变量sub
中,并打印输出。
- 需要注意的是,
substr()
函数返回的是一个新的字符串,不会修改原始字符串。
当然,在
C++
中,substr()
函数也可以只传递一个参数,表示从指定位置开始提取到字符串的末尾的子串。
- 下面是一个示例代码,演示如何使用只有一个参数的
substr()
函数:
#include <iostream>
#include <string>
int main() {
std::string str = "Hello,World!";
// 提取从索引7开始到字符串末尾的子串
std::string sub = str.substr(6);
std::cout << sub << std::endl; // 输出 "World!"
return 0;
}
- 在上面的示例中,我们使用
substr()
函数只传递了一个参数,即起始位置。这将提取从索引6开始到字符串末尾的子串,并将其存储在变量sub
中,然后打印输出。
2. goto语句:
- C语言中的
goto
语句是一种无条件跳转语句,它可以将程序的执行直接转移到指定的标签位置。goto
语句可以用于跳转到程序中的任意位置,包括循环、条件语句、函数等。
下面是goto
语句的基本语法:
goto label;
...
label: statement;
goto
后面是标志符label
,它是用户定义的标签(标签名称必须是唯一);- 将标签放在需要跳转执行的地方前面且以冒号
:
结尾。 label
后面是一个语句,该语句是在goto
语句被执行时要跳转到的位置。
下面是一个使用goto
语句的示例代码:
#include <stdio.h>
int main() {
int i = 1;
loop:
printf("i = %d\n", i);
i++;
if (i <= 5) {
goto loop; // 跳转到标签loop处
}
return 0;
}
- 在上面的示例中,我们使用
goto
语句在循环中实现了一个简单的计数器。首先定义了变量i
并初始化为1,然后使用loop
作为标签。在循环中,我们打印输出i
的值,然后将i
递增。如果i
小于等于5,就跳转到标签loop
处继续执行循环。这样,我们就实现了一个简单的计数器。
但需要注意的是,
goto
语句容易导致程序结构混乱,使代码难以理解和维护。因此,在实际编程中,应尽量避免使用goto
语句,而是使用更结构化的控制流语句(如循环和条件语句)来组织代码。
3. string::clear()函数:
- 在C++的string库中,
clear()
函数用于清空字符串的内容,即将字符串中的所有字符都删除,使字符串变为空字符串。
下面是clear()
函数的语法:
void clear();
clear()
函数没有参数。- 调用
clear()
函数将清空字符串的内容。
下面是一个使用clear()
函数的示例代码:
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
std::cout << "Before clear: " << str << std::endl; // 输出 "Before clear: Hello, World!"
str.clear();
std::cout << "After clear: " << str << std::endl; // 输出 "After clear: "
return 0;
}
- 在上面的示例中,我们首先定义了一个字符串
str
,并将其初始化为"Hello, World!"。然后,我们使用clear()
函数清空了字符串的内容。在调用clear()
函数后,字符串变为空字符串。最后,我们通过打印输出来验证字符串是否已经清空。
但需要注意的是,
clear()
函数只清空字符串的内容,而不会释放字符串所占用的内存。字符串对象的容量(即分配的内存大小)不会改变。如果需要释放字符串所占用的内存,可以使用shrink_to_fit()
函数。
4. shrink_to_fit()函数:
- 在C++的string库中,
shrink_to_fit()
函数用于将字符串的容量调整为适合其当前大小的最小值。这意味着它可能会释放字符串中未使用的内存空间,从而减少内存的占用。
下面是shrink_to_fit()
函数的语法:
void shrink_to_fit();
shrink_to_fit()
函数没有参数。- 调用
shrink_to_fit()
函数将使字符串的容量调整为适合其当前大小的最小值。
下面是一个使用shrink_to_fit()
函数的示例代码:
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
std::cout << "Before shrink_to_fit: capacity = " << str.capacity() << std::endl; // 输出 "Before shrink_to_fit: capacity = 14"maybe
str.shrink_to_fit();
std::cout << "After shrink_to_fit: capacity = " << str.capacity() << std::endl; // 输出 "After shrink_to_fit: capacity = 13"maybe
return 0;
}
- 在上面的示例中,我们首先定义了一个字符串
str
,并将其初始化为"Hello, World!"。然后,我们使用capacity()
函数获取字符串的容量,并打印输出。在调用shrink_to_fit()
函数后,我们再次使用capacity()
函数获取字符串的容量,并打印输出。可以看到,调用shrink_to_fit()
函数后,字符串的容量可能会从14减少到了13,即可能会释放1个字节的内存空间。
但需要注意的是,根据
C++
标准,shrink_to_fit()
函数是一个非强制性的操作,实现可能会忽略该请求。因此,调用shrink_to_fit()
函数并不一定会导致字符串的容量减小。实际上,大多数实现中的
string
类会动态地管理内存,根据需要自动分配和释放内存。当我们从字符串中删除字符时,实现通常会自动释放相应的内存,而不需要显式调用shrink_to_fit()
函>数。
- 本节我们主要学习了如何在一个句子中找到并打印出第一个出现的最长单词,用了几种不同的方法去实现,但是殊途同归,基本的思路是一致的。除此之外,我们还学习了
substr()
函数,goto
语句,clear()
函数等等。