1 算法题 :删除字符串中连续重复的字符
1.1 题目含义
题目要求删除字符串中连续重复的字符,即对于字符串中的每个字符,如果它与其前面的字符相同,则删除该字符。重复此过程,直到没有连续重复的字符为止。
1.2 示例
示例 1:
输入:"aabbbcccddeeeee"
输出:"abcde"
解释:从输入字符串开始,首先删除两个连续的 'a',得到 "abbbcccddeeeee"。接着删除三个连续的 'b',得到 "abcccddeeeee"。然后删除三个连续的 'c',得到 "abddeeeee"。再删除四个连续的 'e',最终得到 "abcde"
示例 2:
输入:"abcddeffg"
输出:"abcdfg"
解释:在这个例子中,没有连续重复的字符,所以直接返回原字符串。
示例 3:
输入:"aabbbcccaaa"
输出:"abca"
解释:首先删除两个连续的 'a',得到 "abbbcccaaa"。然后删除三个连续的 'b',得到 "abcccaa"。接着删除三个连续的 'c',得到 "abcaa"。最后删除三个连续的 'a',得到最终的结果 "abca"。
2 解题思路
解题思路如下:
为了删除字符串中连续重复的字符,可以使用双指针方法。其中一个指针 i 用来遍历字符串,而另一个指针 j 用来构建结果字符串。
步骤如下:
(1)初始化指针:
初始化两个指针 i 和 j,都指向字符串的起始位置。
初始化一个空字符串 result 用于存储最终结果。
(2)遍历字符串:
使用 i 指针遍历输入字符串的每个字符。
当 i 指针遇到与 i - 1 指针指向的字符不同的字符时,说明找到了一个新的字符序列的开始。
(3)处理连续重复字符:
当 i 指针遇到与 i - 1 指针指向的字符相同的字符时,说明遇到了连续重复字符,跳过这些字符。
重复此过程,直到 i 指针遇到与 i - 1 不同的字符为止。
(4)构建结果字符串:
当 i 指针遇到一个新的字符序列的开始时,将 j 指针所指的字符添加到 result 字符串中,并更新 j 指针的位置。
重复此过程,直到遍历完整个输入字符串。
(5)返回结果:
返回构建好的 result 字符串作为最终结果。
该算法时间复杂度是 O(n),其中 n 是输入字符串的长度。这是因为算法只遍历了一次输入字符串,并且在遍历过程中,对于每个字符只进行了常数时间的操作,如比较字符和更新结果字符串的指针。
算法中使用了两个指针 i 和 j,但它们都最多遍历了 n 个位置,其中 i 指针用于遍历输入字符串,而 j 指针用于构建结果字符串。由于每个字符的比较和赋值操作都是常数时间的,所以算法的总时间复杂度是线性的,即 O(n)。
空间复杂度方面,算法使用了一个额外的字符串 result 来存储结果。在最坏的情况下,即输入字符串中所有字符都是不同的,result 的长度将与输入字符串相同,即 n。因此,空间复杂度也是 O(n)。然而,如果输入字符串中有许多连续重复的字符,那么 result 的长度可能会远小于 n。但无论如何,空间复杂度的上界都是 O(n)。
3 算法实现代码
3.1 使用双指针方法
如下为算法实现代码:
cpp
#include <iostream>
#include <string>
class Solution
{
public:
std::string removeDuplicateLetters(const std::string& str) {
std::string result;
result.resize(str.length());
int j = 0; // 结果字符串的指针
for (int i = 0; i < str.size(); ++i) {
// 当遇到新字符时,将前一个字符添加到结果字符串中
if (i > 0 && str[i] != str[i - 1]) {
result[j++] = str[i - 1];
}
// 处理最后一个字符的情况
if (i == str.size() - 1) {
result[j++] = str[i];
}
}
// 截断结果字符串,移除末尾的空字符
result.resize(j);
return result;
}
};
上面代码使用了 j 指针来记录结果字符串的当前位置,只有在遇到新字符序列的开始时,才将字符添加到结果字符串中。这样可以确保结果字符串中不包含连续重复的字符。注意:需要特别处理字符串的最后一个字符,因为在循环中它不会被检查是否与前一个字符相同。最后,使用 resize 方法来移除结果字符串末尾的空字符。
调用上面的算法,并得到输出:
cpp
int main()
{
Solution ss;
std::string input = "aabbbcccddeeeeeaa";
std::string output = ss.removeDuplicateLetters(input);
std::cout << "Input: " << input << std::endl;
std::cout << "Output: " << output << std::endl;
return 0;
}
上面代码的输出为:
Input: aabbbcccddeeeeeaa
Output: abcdea
3.2 使用栈实现的算法
栈在这里用于存储不连续的字符,每当遇到一个新的字符时,就将其压入栈中。如果下一个字符与栈顶字符相同,则弹出栈顶字符;如果不同,则继续压入栈中。这样,栈中始终存储的是不连续的字符。
如下为算法实现代码:
cpp
#include <iostream>
#include <stack>
#include <string>
class Solution
{
public:
std::string removeDuplicateLetters(const std::string& str) {
std::stack<char> stk;
std::string result;
for (char c : str) {
if (stk.empty() || stk.top() != c) {
// 如果当前字符与栈顶字符不同,则将其压入栈中
stk.push(c);
}
}
// 将栈中的字符弹出并构建成结果字符串
while (!stk.empty()) {
result.push_back(stk.top());
stk.pop();
}
// 由于栈是后进先出的,我们需要反转结果字符串
std::reverse(result.begin(), result.end());
return result;
}
};
这个算法的时间复杂度依然是 O(n),其中 n 是输入字符串的长度。算法遍历了输入字符串一次,对于每个字符,最多执行了一次压栈操作和一次弹栈操作,这些都是常数时间操作。因此,总的时间复杂度是线性的。
空间复杂度方面,最坏情况下,当输入字符串中所有字符都是不同的时,栈的大小将是 n。因此,空间复杂度的上界是 O(n)。在平均情况下,如果输入字符串中有许多连续重复的字符,那么栈的大小可能会远小于 n(而上面的第一种算法,则一开始就要创建一个大小为 n 的空间)。但是,我们依然可以说空间复杂度的上界是 O(n)。
4 测试用例
以下是针对上面算法的测试用例,基本覆盖了各种情况:
(1)基础测试用例
- 输入:"aabbbcccddeeeee"
- 预期输出:"abcde"
- 解释:连续重复的字符被删除,保留不连续的字符。
(2)不包含连续重复字符的测试用例
- 输入:"abcdefg"
- 预期输出:"abcdefg"
- 解释:输入字符串中没有连续重复字符,所以输出与输入相同。
(3)全为连续重复字符的测试用例
- 输入:"aaaaa"
- 预期输出:"a"
- 解释:所有字符都是连续的,所以只保留一个。
(4)单个字符字符串的测试用例
- 输入:"a"
- 预期输出:"a"
- 解释:单个字符不构成连续重复,因此输出与输入相同。
(5)空字符串的测试用例
- 输入:""
- 预期输出:""
- 解释:空字符串不包含任何字符,因此输出为空。
(6)混合连续和非连续重复字符的测试用例
- 输入:"aaabbbcccddeeeeefffabcggg"
- 预期输出:"abcdefabcg"
- 解释:删除连续重复的字符,保留非连续的字符。
(7)包含数字字符以及特殊字符的测试用例
- 输入:"1122aaa3344bb&&&556677889900(("
- 预期输出:"12a34b&567890("
- 解释:数字字符以及特殊字符的连续重复也应被正确删除。