1 算法题 :判断一个字符串是否包含另一个字符串的所有字符(不一定连续)
1.1 题目含义
判断一个字符串(称为"主字符串"或"大字符串")是否包含另一个字符串(称为"子字符串"或"小字符串")的所有字符,且不论这些字符在主字符串中的顺序和连续性。换句话说,我们要确认子字符串的每一个字符至少在主字符串中出现一次。
1.2 示例
示例 1:
输入:
- 主字符串: "abcdefghijklmnopqrstuvwxyz"
- 子字符串: "abcde"
输出: true
解释: 主字符串包含子字符串的所有字符。
示例 2:
输入:
- 主字符串: "mississippi"
- 子字符串: "missip"
输出: true
解释: 尽管 "missip" 在主字符串中不是连续的,但主字符串包含子字符串的所有字符。
示例 3:
输入:
- 主字符串: "abcdefg"
- 子字符串: "defghijklmnop"
输出: false
解释: 主字符串不包含子字符串的所有字符,因为 "hijklmnop" 中的字符在主字符串中没有出现。
2 解题思路
解题思路如下:
(1)初始化哈希表:
在 C++ 中,我们可以使用std::unordered_map来作为哈希表。首先,遍历子字符串,对于每个字符,我们在unordered_map中增加其计数。
(2)遍历主字符串并更新哈希表:
然后,遍历主字符串。对于主字符串中的每个字符,我们检查它是否在unordered_map中。如果在,则将其对应的计数减一。如果不在,说明主字符串中缺少该字符,因此可以立即返回false。
(3)检查哈希表:
在遍历完主字符串后,我们检查 unordered_map 中所有字符的计数是否都已经归零。如果所有计数都为零,说明主字符串包含了子字符串的所有字符,返回 true;如果存在任何非零计数,说明主字符串缺少某些字符,返回 false。
(4)返回结果:
根据unordered_map的状态,返回相应的布尔值。
3 算法实现代码
3.1 使用哈希表
如下为算法实现代码:
cpp
#include <iostream>
#include <unordered_map>
#include <string>
class Solution
{
public:
bool isContainsAllCharacters(const std::string& mainString, const std::string& subString) {
// 初始化哈希表
std::unordered_map<char, int> charCounts;
for (char c : subString) {
charCounts[c]++;
}
// 遍历主字符串并更新哈希表
for (char c : mainString) {
if (charCounts.find(c) != charCounts.end()) {
charCounts[c]--;
if (charCounts[c] == 0) {
// 如果字符的计数减到0,从哈希表中删除该字符
charCounts.erase(c);
}
}
}
// 检查哈希表
return charCounts.empty(); // 如果哈希表为空,说明所有字符都被找到了
}
};
在 isContainsAllCharacters 成员函数中,首先使用std::unordered_map<char, int>创建了一个名为charCounts的哈希表。这个哈希表将存储 subString中 每个字符及其出现的次数(遍历 subString 中的每个字符 c,并在 charCounts 中增加该字符的计数。如果字符 c 在 charCounts 中不存在,它会被插入到哈希表中并设置计数为 1;如果它已经存在,则计数增加 1)。
接下来,使用 find 方法来检查字符 c 是否存在于 charCounts 中。如果字符 c 在 charCounts 中存在,将其计数减 1。如果减1后计数变为 0,说明 mainString 中的这个字符与 subString 中的字符数量相匹配,可以从 charCounts中 删除这个字符。
最后,如果哈希表为空,说明 mainString 包含了 subString 中的所有字符,因为所有在 subString 中出现的字符都已经在 mainString 中被匹配并从哈希表中删除了。如果哈希表不为空,则意味着至少有一个 subString 中的字符在 mainString 中没有出现。
调用上面的算法,并得到输出:
cpp
int main()
{
Solution solution;
std::string mainString = "abcdefghijklmnopqrstuvwxyz";
std::string subString = "abcde";
std::cout << "Does \"" << mainString << "\" contain all characters of \"" << subString << "\"? "
<< (solution.isContainsAllCharacters(mainString, subString) ? "Yes" : "No") << std::endl;
mainString = "mississippi";
subString = "missip";
std::cout << "Does \"" << mainString << "\" contain all characters of \"" << subString << "\"? "
<< (solution.isContainsAllCharacters(mainString, subString) ? "Yes" : "No") << std::endl;
mainString = "abcdefg";
subString = "defghijklmnop";
std::cout << "Does \"" << mainString << "\" contain all characters of \"" << subString << "\"? "
<< (solution.isContainsAllCharacters(mainString, subString) ? "Yes" : "No") << std::endl;
return 0;
}
上面代码的输出为:
Does "abcdefghijklmnopqrstuvwxyz" contain all characters of "abcde"? Yes
Does "mississippi" contain all characters of "missip"? Yes
Does "abcdefg" contain all characters of "defghijklmnop"? No
3.2 使用排序和比较的方法
这种方法的思路是,如果主字符串包含子字符串的所有字符,那么对两个字符串进行排序后,排序后的子字符串应该是排序后的主字符串的一个子序列。该方法的优点是它不需要使用额外的数据结构来记录字符的出现次数,而是通过比较排序后的字符串来直接得出结论。然而,它的缺点是需要对字符串进行排序,这可能会增加一些计算复杂度。
如下为算法实现代码:
cpp
#include <iostream>
#include <cctype>
#include <string>
#include <algorithm> // 用于std::sort
class Solution
{
public:
bool isContainsAllCharacters(const std::string& mainString, const std::string& subString) {
// 对两个字符串进行排序
std::string sorted_main = mainString;
std::string sorted_sub = subString;
std::sort(sorted_main.begin(), sorted_main.end());
std::sort(sorted_sub.begin(), sorted_sub.end());
// 使用双指针法检查 sorted_sub 是否是 sorted_main 的子序列
int i = 0, j = 0;
while (i < sorted_sub.length() && j < sorted_main.length()) {
if (sorted_sub[i] == sorted_main[j]) {
i++; // 移动到 sorted_sub 的下一个字符
}
j++; // 移动到 sorted_main 的下一个字符
}
// 如果 sorted_sub 的所有字符都在 sorted_main 中找到,则 sorted_sub 是 sorted_main 的子序列
return sorted_sub.length() == i;
}
};
在这个实现中,首先使用 std::sort 函数对这两个字符串进行排序。排序后,sorted_main 和 sorted_sub 分别包含与 mainString 和 subString 相同但已排序的字符。
然后,使用双指针法检查 sorted_sub 是否是 sorted_main 的子序列(注意:无论是否找到匹配的字符,都将 j 增加 1,以继续检查 sorted_main 的下一个字符)。
接着,判断 sorted_sub 的所有字符是否都在 sorted_main 中找到(如果 sorted_sub 的所有字符都在 sorted_main 中找到,那么 i 的值应该等于 sorted_sub 的长度)。
4 测试用例
以下是针对上面算法的测试用例:
(1)基础测试用例:
输入:
- 主字符串: "abcdefg"
- 子字符串: "bcd"
输出: true
解释: 主字符串包含子字符串的所有字符。
(2)包含所有字符但顺序不同:
输入:
- 主字符串: "bacdefg"
- 子字符串: "abc"
输出: true
解释: 虽然字符顺序不同,但主字符串包含子字符串的所有字符符。
(3)缺少一个字符:
输入:
- 主字符串: "abdefg"
- 子字符串: "abc"
输出: false
解释: 主字符串缺少子字符串中的一个字符 'c'。
(4)包含重复字符:
输入:
- 主字符串: "aabbbccc"
- 子字符串: "abc"
输出: true
解释: 即使主字符串中的字符有重复,但它仍然包含子字符串中的所有字符。
(5)子字符串是主字符串的前缀:
输入:
- 主字符串: "abcdefg"
- 子字符串: "abc"
输出: true
解释: 子字符串是主字符串的前缀,因此主字符串必然包含子字符串中的所有字符。
这些测试用例涵盖了不同的场景,从基础情况到边缘情况,有助于验证代码的正确性和健壮性。