1 算法题 :找到字符串中第一个不重复的字符
1.1 题目含义
给定一个字符串(字符串中只包含小写字母,且长度不超过1000),找到它的第一个只出现一次的字符,并返回它的索引。如果不存在,则返回-1。
这个题目要求在给定的字符串中,找到第一个只出现一次的字符。这意味着需要遍历整个字符串,记录每个字符出现的次数,然后再次遍历字符串,找到第一个出现次数为 1 的字符,并返回它的索引。如果字符串中没有只出现一次的字符,那么返回 -1。
1.2 示例
示例 1:
输入: “abcabcbb”
输出: 2
解释: ‘c’ 在原字符串中只出现一次,并且其位于索引 2。
示例 2:
输入: “bbbfbbff”
输出: -1
解释:字符串中没有只出现一次的字符,因此返回 -1。
示例 3:
输入: “abcd”
输出: 0
解释: ‘a’ 在原字符串中只出现一次,并且其位于索引 0。
2 解题思路
解题思路如下:
可以采用 C++ 的 STL 容器来辅助解决问题。考虑到字符串中只包含小写字母,并且需要快速判断某个字符是否只出现了一次,因此可以使用 std::unordered_map(哈希表)来记录每个字符及其出现的次数。
(1)初始化哈希表: 首先,创建一个 std::unordered_map<char, int>,其中键是字符,值是字符出现的次数。初始时,所有字符的出现次数都设为 0。
(2)遍历字符串并记录次数: 然后,遍历输入的字符串。对于字符串中的每个字符,我们在哈希表中增加其对应的次数。如果字符已存在于哈希表中,则将其次数加 1;如果字符不存在,则在哈希表中插入该字符,并将其次数设为1。
(3)再次遍历字符串找只出现一次的字符: 接下来,再次遍历字符串。对于每个字符,检查它在哈希表中的次数。如果次数为 1,说明这个字符只出现了一次,则返回它的索引。
(4)处理不存在只出现一次字符的情况: 如果遍历完整个字符串都没有找到只出现一次的字符,则说明不存在这样的字符,则返回 -1。
这种方法的时间复杂度是 O(n),其中 n 是字符串的长度,因为需要遍历两次字符串。空间复杂度是 O(1),因为字符集是固定的(只有小写字母),所以哈希表的大小是固定的,不随输入规模的增大而增大。然而,严格来说,由于使用了哈希表,空间复杂度实际上是 O(k),其中 k 是字符集的大小(在这里是 26),但由于 k 是一个常数,通常还是将其视为O(1)。
这种解题思路利用了哈希表快速查找和更新的特性,使得算法能够在一次遍历中统计字符出现次数,并在另一次遍历中找到只出现一次的字符。虽然需要遍历两次字符串,但由于哈希表的操作非常高效,因此整体效率仍然很高。
3 算法实现代码
3.1 使用哈希表
如下为算法实现代码:
#include <iostream>
#include <string>
#include <unordered_map>
class Solution
{
public:
int firstUniqChar(const std::string& s)
{
// 初始化哈希表,用于记录每个字符出现的次数
std::unordered_map<char, int> charCount;
// 第一次遍历字符串,统计字符出现次数
for (char c : s) {
if (c >= 'a' && c <= 'z') {
charCount[c]++;
}
}
// 第二次遍历字符串,查找只出现一次的字符
for (int i = 0; i < s.length(); ++i) {
char c = s[i];
if (c >= 'a' && c <= 'z' && charCount[c] == 1) {
// 找到第一个只出现一次的字符,返回其索引
return i;
}
}
// 没有找到只出现一次的字符,返回-1
return -1;
}
};
在上面代码中,首先定义了一个 firstUniqChar 函数,它接受一个字符串 s 作为参数,并返回第一个只出现一次的字符的索引。然后使用 std::unordered_map<char, int> 来存储每个字符及其出现的次数。第一次遍历字符串 s,将每个字符的出现次数记录在 charCount 中。然后,在第二次遍历中,检查每个字符的出现次数,如果某个字符只出现了一次,就返回它的索引。如果遍历完整个字符串都没有找到只出现一次的字符,则返回-1。
调用上面的算法,并得到输出:
int main()
{
Solution s;
// 示例测试
std::string str1 = "abcabcbb";
std::cout << "First unique character in \"" << str1 << "\" is at index: " << s.firstUniqChar(str1) << std::endl;
std::string str2 = "bbbfbbff";
std::cout << "First unique character in \"" << str2 << "\" is at index: " << s.firstUniqChar(str2) << std::endl;
std::string str3 = "abcd";
std::cout << "First unique character in \"" << str3 << "\" is at index: " << s.firstUniqChar(str3) << std::endl;
return 0;
}
上面代码的输出为:
First unique character in "abcabcbb" is at index: -1
First unique character in "bbbfbbff" is at index: -1
First unique character in "abcd" is at index: 0
3.2 使用数组
如下为算法实现代码:
#include <iostream>
#include <string>
#include <vector>
class Solution
{
public:
int firstUniqChar(const std::string& s)
{
// 初始化一个大小为26的数组,用于记录每个字母出现的次数
std::vector<int> charCount(26, 0);
// 遍历字符串,统计每个字母出现的次数
for (char c : s) {
if (c >= 'a' && c <= 'z') {
charCount[c - 'a']++; // 将字符转换为数组下标
}
}
// 再次遍历字符串,查找只出现一次的字符
for (int i = 0; i < s.length(); ++i) {
char c = s[i];
if (c >= 'a' && c <= 'z' && charCount[c - 'a'] == 1) {
charCount[c - 'a']++; // 将字符转换为数组下标
return i; // 返回字符的索引
}
}
// 没有找到只出现一次的字符
return -1;
}
};
在这个实现中,使用了一个大小为 26 的 std::vector<int>数组 charCount,其中每个元素初始化为 0。由于小写字母的 ASCII 码是连续的,所以可以通过字符 ‘a’ 的 ASCII 码值将字符映射到数组的相应位置。
第一次遍历字符串时,增加 charCount 数组中对应字符出现次数的值。第二次遍历字符串时,检查每个字符在 charCount 数组中的值是否为 1,如果是,则返回该字符的索引。如果遍历完整个字符串都没有找到只出现一次的字符,则返回 -1。
这种方法的空间复杂度是 O(1),因为数组的大小是固定的,不随输入字符串的长度而变化。时间复杂度仍然是 O(n),其中 n 是字符串的长度,这是因为需要遍历字符串两次。这种实现方法比使用哈希表更节省空间,但牺牲了哈希表带来的灵活性(例如,不能处理非小写字母字符)。
4 测试用例
以下是针对上面算法的测试用例,基本覆盖了各种情况:
(1)基础测试用例
输入:s = “abcabcbb”
输出:2
说明:字符 ‘c’ 在原字符串中只出现一次,并且其位于索引 2。
(2)无只出现一次字符的测试用例
输入:s = “bbbfbbff”
输出:-1
说明:字符串中没有只出现一次的字符,因此返回 -1。
(3)第一个字符即为只出现一次的字符
输入:s = “abcd”
输出:0
说明:字符 ‘a’ 在原字符串中只出现一次,并且其位于索引 0。
(4)只出现一次的字符在字符串末尾
输入:s = “zzxyzz”
输出:3
说明:字符 ‘x’ 在原字符串中只出现一次,并且其位于索引 3。
(5)只出现一次的字符在字符串中间
输入:s = “aabaac”
输出:4
说明:字符 ‘c’ 在原字符串中只出现一次,并且其位于索引 4。
(6)包含多个只出现一次的字符,但返回第一个
输入:s = “aabbcde”
输出:3
说明:尽管 ‘c’ 和 ‘d’ 也只出现一次,但 ‘b’ 是第一个只出现一次的字符,因此返回索引 3。
(7)字符串长度为1的测试用例
输入:s = “a”
输出:0
说明:当字符串只包含一个字符时,该字符自然只出现一次,返回索引 0。
(8)字符串为空的情况
输入:s = “”
输出:-1
说明:空字符串中没有字符,因此返回 -1。
(9)大小写混合的测试用例(注意:题目中说明只包含小写字母,但这里为了测试算法的健壮性,仍然给出此用例)
输入:s = “AaBbCc”
输出:-1
说明:尽管每个字符在字符串中只出现一次,但题目要求只考虑小写字母,因此返回 -1。
(10)包含特殊字符的测试用例(注意:题目中说明只包含小写字母,但这里为了测试算法的健壮性,仍然给出此用例)
输入:s = “abc!@#”
输出:-1
说明:字符串中包含非小写字母的字符,因此返回 -1。