在算法面试和日常编程中,字符串处理是一个非常常见且重要的话题。今天,我将详细分析两个经典的字符串处理问题:仅反转字母和查找字符串中的第一个唯一字符。这两个问题虽然看似简单,但包含了很多字符串处理的基本思想和技巧。
LeetCode 917. 仅仅反转字母
给你一个字符串 s,根据下面的算法反转字符串:
返回反转后的字符串。
示例 1:
输入:s = "ab-cd"
输出:"dc-ba"示例 2:
输入:s = "a-bC-dEf-ghIj"
输出:"j-Ih-gfE-dCba"示例 3:
输入:s = "Test1ng-Leet=code-Q!"
输出:"Qedo1ct-eeLg=ntse-T!"这个问题的核心是:只反转字母,其他字符保持原位。
解决这类问题的常用方法是双指针技术。我们可以使用两个指针,一个从字符串开头向右移动,另一个从字符串末尾向左移动。当两个指针都指向字母时,交换这两个字母;如果某个指针指向的不是字母,则该指针继续移动,直到找到一个字母或者两个指针相遇。
class Solution {
public:
// 判断字符是否为字母
bool isLetter(char ch) {
// 小写字母a-z或大写字母A-Z
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
return true;
return false;
}
string reverseOnlyLetters(string s) {
size_t begin = 0; // 左指针,从头开始
size_t end = s.size() - 1; // 右指针,从尾开始
while (begin < end) {
// 移动左指针直到找到字母
while (begin < end && !isLetter(s[begin]))
++begin;
// 移动右指针直到找到字母
while (begin < end && !isLetter(s[end]))
--end;
// 交换两个字母
swap(s[begin], s[end]);
// 移动指针继续查找下一对字母
++begin;
--end;
}
return s;
}
};这个算法已经是最优的了,因为我们必须至少遍历一次字符串才能找出所有的字母。双指针方法避免了使用额外的空间来存储字母的位置,是一种非常高效的解决方案。
LeetCode 387. 字符串中的第一个唯一字符
给定一个字符串 s,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
示例 1:
输入: s = "leetcode"
输出: 0示例 2:
输入: s = "loveleetcode"
输出: 2示例 3:
输入: s = "aabb"
输出: -1要找到字符串中第一个不重复的字符,我们需要知道每个字符出现的次数。这个问题可以分为两步:
由于题目说明字符串只包含小写字母,我们可以使用一个长度为26的数组来记录每个字母出现的次数,这比使用哈希表更加高效。
class Solution {
public:
int firstUniqChar(string s) {
// 初始化计数数组,记录每个小写字母出现的次数
int count[26] = {0};
// 第一次遍历:统计每个字符出现的次数
for (char c : s) {
count[c - 'a']++; // 将字符映射到0-25的索引
}
// 第二次遍历:找到第一个出现次数为1的字符
for (int i = 0; i < s.size(); ++i) {
if (count[s[i] - 'a'] == 1) {
return i; // 返回该字符的索引
}
}
// 如果没有找到唯一字符,返回-1
return -1;
}
};这个算法已经是最优的了,因为我们必须至少遍历一次字符串才能统计每个字符出现的次数,然后再遍历一次才能按原顺序找到第一个唯一字符。
如果字符集不限于小写字母,我们可以使用哈希表代替固定大小的数组,但时间复杂度和空间复杂度的分析基本相同。
通过上面两个问题,我们可以总结出一些处理字符串问题的通用技巧:
双指针是处理字符串问题的常用技术,特别是当需要在字符串中查找、比较或修改元素时。双指针可以分为几种类型:
当需要统计字符出现次数或频率时,哈希表或计数数组是非常有用的工具:
这两个字符串处理问题在实际开发中有很多应用场景:
通过这两个问题,我们学习了字符串处理的基本技巧:双指针法和哈希统计法。这些技巧不仅适用于这两个特定问题,还可以应用于许多其他字符串处理问题。
如果你已经掌握了这两个问题,可以尝试以下进阶问题:
字符串处理是算法中的重要部分,掌握这些基本技巧将帮助你解决更复杂的问题。希望这篇文章对你有所帮助!