引言
在字符串处理中,获取最后一个单词的长度是一个常见问题。这个问题在文本处理、编译原理、自然语言处理等领域都有广泛应用。本文将详细介绍多种解决该问题的方法,并分析各自的优缺点。
目录
[1. 空字符串](#1. 空字符串)
[2. 全是空格](#2. 全是空格)
[3. 末尾有多个空格](#3. 末尾有多个空格)
[1. 获取倒数第二个单词的长度](#1. 获取倒数第二个单词的长度)
[2. 统计句子中单词的数量](#2. 统计句子中单词的数量)
[3. 获取最长的单词](#3. 获取最长的单词)
[1. 命令行工具](#1. 命令行工具)
[2. 文本编辑器](#2. 文本编辑器)
[3. 日志分析](#3. 日志分析)
[4. 自然语言处理](#4. 自然语言处理)
[1. 使用引用避免拷贝](#1. 使用引用避免拷贝)
[2. 预分配内存](#2. 预分配内存)
[3. 使用C风格字符串](#3. 使用C风格字符串)
[1. 忘记处理npos](#1. 忘记处理npos)
[2. 未考虑末尾空格](#2. 未考虑末尾空格)
[3. 越界访问](#3. 越界访问)
问题描述
给定一个由多个单词组成的句子,每个单词由大小写字母混合构成,单词间使用单个空格分隔。要求输出最后一个单词的长度。
约束条件:
-
每个单词非空
-
总字符长度不超过 103103
-
单词间使用单个空格分隔
示例:
text
输入:HelloNowcoder
输出:13
输入:A B C D
输出:1
解法一:从后向前遍历法(推荐)
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
int len = 0;
int i = str.size() - 1;
// 跳过末尾可能的空格(虽然题目说没有,但增加鲁棒性)
while (i >= 0 && str[i] == ' ') i--;
// 计算最后一个单词的长度
while (i >= 0 && str[i] != ' ') {
len++;
i--;
}
cout << len << endl;
return 0;
}
算法分析
时间复杂度: O(n)
- 最坏情况下需要遍历整个字符串
空间复杂度: O(1)
- 只使用了常数个额外变量
优点:
-
高效:只需要一次遍历
-
节省空间:不需要额外存储
-
鲁棒性好:能处理末尾有空格的情况
解法二:使用rfind方法
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
// 从后向前查找最后一个空格的位置
size_t pos = str.rfind(' ');
if (pos == string::npos) {
// 没有空格,整个字符串就是一个单词
cout << str.size() << endl;
} else {
// 计算最后一个单词的长度
cout << str.size() - pos - 1 << endl;
}
return 0;
}
关键点说明
-
rfind(' '): 从字符串末尾开始查找空格 -
string::npos: 表示未找到,值为-1(但类型为size_t,所以是最大无符号数) -
注意处理只有一个单词的情况
解法三:使用stringstream分割
cpp
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
string str;
getline(cin, str);
stringstream ss(str);
string word, last_word;
// 读取所有单词,最后一个存储在last_word中
while (ss >> word) {
last_word = word;
}
cout << last_word.size() << endl;
return 0;
}
算法特点
优点:
-
代码简洁易读
-
自动处理多余空格
-
容易扩展(如需要处理所有单词)
缺点:
-
需要额外的字符串拷贝
-
使用stringstream有额外开销
-
需要存储最后一个单词的完整副本
解法四:双指针法
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
int right = str.size() - 1;
// 右指针:跳过末尾空格
while (right >= 0 && str[right] == ' ') right--;
int left = right;
// 左指针:找到单词开头
while (left >= 0 && str[left] != ' ') left--;
// 计算长度
cout << right - left << endl;
return 0;
}
算法性能对比
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 从后向前遍历 | O(n) | O(1) | 效率高,内存少 | 需要手动处理边界 |
| rfind方法 | O(n) | O(1) | 代码简洁 | 需要处理npos |
| stringstream | O(n) | O(n) | 自动处理空格 | 额外开销大 |
| 双指针法 | O(n) | O(1) | 思路清晰 | 需要两个指针 |
边界条件处理
1. 空字符串
cpp
// 在从后向前遍历法中
if (str.empty()) {
cout << 0 << endl;
return 0;
}
2. 全是空格
cpp
// 在从后向前遍历法中,第一个while循环后i可能为-1
if (i < 0) {
cout << 0 << endl;
return 0;
}
3. 末尾有多个空格
cpp
// 所有方法都应该处理这种情况
// 解法一和四已经通过while循环处理
扩展问题
1. 获取倒数第二个单词的长度
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
int count = 0;
int i = str.size() - 1;
// 跳过末尾空格
while (i >= 0 && str[i] == ' ') i--;
// 找倒数第一个单词
while (i >= 0 && str[i] != ' ') i--;
// 跳过单词间的空格
while (i >= 0 && str[i] == ' ') i--;
// 找倒数第二个单词的末尾
int end = i;
while (i >= 0 && str[i] != ' ') i--;
cout << end - i << endl;
return 0;
}
2. 统计句子中单词的数量
cpp
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
string str;
getline(cin, str);
stringstream ss(str);
string word;
int count = 0;
while (ss >> word) {
count++;
}
cout << count << endl;
return 0;
}
3. 获取最长的单词
cpp
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
string str;
getline(cin, str);
stringstream ss(str);
string word, longest_word;
int max_len = 0;
while (ss >> word) {
if (word.size() > max_len) {
max_len = word.size();
longest_word = word;
}
}
cout << "最长单词: " << longest_word << ", 长度: " << max_len << endl;
return 0;
}
实际应用场景
1. 命令行工具
cpp
// 实现类似Linux的wc命令,统计单词数
// 可以扩展为获取最后一行或最后一个单词
2. 文本编辑器
cpp
// 在代码编辑器中,获取光标所在单词的长度
// 用于自动补全或语法高亮
3. 日志分析
cpp
// 分析日志文件的最后一个状态码或错误信息
4. 自然语言处理
cpp
// 在NLP预处理中,获取句子的最后一个词
// 用于语言模型训练或情感分析
测试用例
cpp
void test() {
// 测试用例集合
vector<pair<string, int>> test_cases = {
{"HelloNowcoder", 13},
{"A B C D", 1},
{"Hello World", 5},
{"a", 1},
{" ", 0}, // 全是空格
{"hello ", 5}, // 末尾有空格
{" hello", 5}, // 开头有空格
{"multiple spaces between", 7}, // 多个空格
{"", 0}, // 空字符串
{"12345 67890", 5},
{"The quick brown fox jumps over the lazy dog", 3}
};
for (auto& test_case : test_cases) {
// 测试各个算法
cout << "输入: \"" << test_case.first << "\", 期望: " << test_case.second << endl;
}
}
优化技巧
1. 使用引用避免拷贝
cpp
// 在处理大字符串时,使用const引用
void processString(const string& str) {
// 处理逻辑
}
2. 预分配内存
cpp
// 如果知道最大长度,可以预分配
str.reserve(1000); // 根据题目约束
3. 使用C风格字符串
cpp
// 在性能关键场景,可以使用C风格字符串
int getLastWordLength(const char* str) {
int len = 0;
int i = strlen(str) - 1;
while (i >= 0 && str[i] == ' ') i--;
while (i >= 0 && str[i] != ' ') {
len++;
i--;
}
return len;
}
常见错误
1. 忘记处理npos
cpp
// 错误示例
size_t pos = str.rfind(' ');
int length = str.size() - pos - 1; // 当pos为npos时,计算错误
2. 未考虑末尾空格
cpp
// 错误示例
int pos = str.rfind(' ');
if (pos != -1) {
cout << str.size() - pos - 1 << endl; // 如果末尾有空格,结果错误
}
3. 越界访问
cpp
// 错误示例
int i = str.size() - 1;
while (str[i] == ' ') i--; // 如果字符串为空,i为-1,访问越界
总结
获取字符串最后一个单词的长度是一个基础的字符串处理问题,但它涉及了许多重要的编程概念:
-
字符串遍历技巧:从后向前遍历是解决此类问题的关键
-
边界条件处理:空字符串、空格、单个单词等情况都需要考虑
-
算法选择:根据具体需求选择最合适的算法
-
代码鲁棒性:处理各种异常输入情况
推荐方法:从后向前遍历法
-
效率高,空间复杂度低
-
代码清晰,易于理解
-
鲁棒性好,能处理各种边界情况
掌握这个问题的解法不仅能帮助解决类似问题,还能提高字符串处理的基本功。在实际开发中,根据具体场景选择最合适的方法才是最重要的。