非常抱歉,我无法回答您“我的代码中的错误在哪里”的具体问题。
但我能做的是帮助您理解问题、开发算法并展示众多潜在解决方案之一。
问题的标题已经暗示,应该使用什么算法:所谓的“滑动窗口”算法。
你会从 Said Sryheni here 那里找到一个很好的解释。
对于您的问题,我们将使用灵活大小的滑动窗口方法。
我们将逐个字符地遍历源字符串并等待,直到满足某个条件。在这种情况下,直到我们“看到”所有需要搜索的字符。然后,我们会找到一个窗口,所有这些字符都在其中。
在给定的示例中,滑动窗口的结尾始终是源字符串中最后读取的字符。这是因为最后读取的字符满足条件。然后我们需要找到窗口的开始。在这种情况下,仍然满足条件的源字符串中最右边的字符(搜索字符)的位置。
然后我们会继续读取源字符串,等待下一个条件满足。然后我们会重新计算滑动窗口的位置。
顺便说一句。其他字符,除了源字符串中的搜索字符外,只是噪声,只会扩展滑动窗口的宽度。
但是我们如何满足条件呢?
尤其是,由于搜索字符的顺序无关紧要,甚至可以有双字符?
解决方案是我们将“计数”。
首先,我们将统计搜索字符串中所有字符的出现次数。此外,我们将使用第二个计数器来指示是否所有字符都匹配。
然后,在对源字符串进行迭代时,我们将减少我们看到的任何字符的计数器。如果搜索字符的计数达到 0,那么我们将递减“匹配”计数器。并且,如果为 0,我们找到了所有搜索字符并且满足条件。然后我们就可以开始计算窗口位置了。
请注意:我们只会减少匹配计数器,如果在减少字符计数器后,这将是 0。
示例(我将省略带有“x”的噪音):
- 搜索字符串“ABC”,源字符串:“xxAxxxxBBBxCAxx”。
- 初始字符计数器将为 1,1,1,匹配计数器将为 3。
- 读取第一个“A”。计数器:0,1,1 2
- 读取第一个“B”。计数器:0,0,1 1
- 读取第二个“B”。计数器:0,-1,1 1(仅当字符计数器达到 0 时,我们才会减少匹配计数器)。
- 阅读第 3 个“B”。计数器:0,-2,1 1(仅当字符计数器达到 0 时,我们才会减少匹配计数器)。
- 读取第一个“C”。计数器:0,-2,0 0。匹配计数器为 0,条件满足。
请注意。负数的字符数表示更右端有更多相同的字符。
接下来,既然条件已经满足,我们来检查滑动窗口的位置。结束位置很明确。这是源字符串中最后读取的字符。这导致了条件的满足。所以,很简单。
要获取滑动窗口的起始位置,我们将从源字符串的开头开始检查,我们可以在其中找到一个搜索字符。我们将增加它的计数,如果计数大于 0,我们将再次增加匹配计数。如果匹配计数大于 0,我们就找到了一个起始位置。现在计数器:1,-2,0 1
下一次检查的起始位置将递增。我们永远不会从 0 开始,而只会从最后使用的开始位置开始。
好的,找到开始和结束位置后,我们有了第一个窗口,并将寻找可能的更小的窗口。我们将继续读取源字符串并检查
- 计算滑动窗口位置后,计数器为:1,-2,0 1
- 阅读下一个“A”。计数器:0,-2,0 0。再次满足条件。
- 我们继续进行滑动窗口检测。最后一个起始位置指向第一个“A”之后的字符“x”
- 增加起始位置并跳过所有“x”。继续
- 读取第一个“B”。计数器:0,-1,0 0
- 读取第二个“B”。计数器:0,0,0 0
- 读取 3d 'B'。计数器:0,1,0 1. 窗口位置计算完成。起始位置是第3个B,这个窗口比上一个窗口小,拿去吧。
由于源字符串被消耗,我们完成并找到了解决方案。
如何实现。我们将对计数器进行一个小抽象,并将其打包到一个迷你类中。这将封装字符和匹配计数的内部处理,并且可以在以后进行优化。
适用于所有字符类型的计数器可以如下实现:
struct SpecialCounterForGeneralChar {
std::unordered_map<char, int> individualLetter{};
int necessaryMatches{};
SpecialCounterForGeneralChar(const std::string& searchLetters) {
for (const char c : searchLetters) individualLetter[c]++;
necessaryMatches = individualLetter.size();
}
inline void incrementFor(const char c) {
individualLetter[c]++;
if (individualLetter[c] > 0)
++necessaryMatches;
}
inline void decrementFor(const char c) {
individualLetter[c]--;
if (individualLetter[c] == 0)
--necessaryMatches;
}
inline bool allLettersMatched() { return necessaryMatches == 0; }
};
如果我们对输入数据了解更多,例如限制为 8 位字符,我们也可以使用:
struct SpecialCounter {
char individualLetter[256]{};
int necessaryMatches{};
SpecialCounter(const std::string& searchLetters) {
for (const char c : searchLetters) {
if (individualLetter[c] == 0) ++necessaryMatches;
individualLetter[c]++;
}
}
inline void incrementFor(const char c) {
individualLetter[c]++;
if (individualLetter[c] > 0)
++necessaryMatches;
}
inline void decrementFor(const char c) {
individualLetter[c]--;
if (individualLetter[c] == 0)
--necessaryMatches;
}
inline bool allLettersMatched() { return necessaryMatches == 0; }
};
这会比上面的稍快(在给定的限制下)
然后,程序的其余部分将只有 15 行代码。
这里的重要信息是,在我们开始实现第一行代码之前,我们需要考虑很长时间。
一个好的算法和设计,将帮助我们找到一个最佳的解决方案。
请参阅下面的完整示例解决方案:
#include <string>
#include <iostream>
#include <unordered_map>
#include <limits>
using Index = unsigned int;
// We want to hide the implementation of the special counter to the outside world
struct SpecialCounter {
char individualLetter[256]{};
int necessaryMatches{};
SpecialCounter(const std::string& searchLetters) {
for (const char c : searchLetters) {
if (individualLetter[c] == 0) ++necessaryMatches;
individualLetter[c]++;
}
}
inline void incrementFor(const char c) {
individualLetter[c]++;
if (individualLetter[c] > 0)
++necessaryMatches;
}
inline void decrementFor(const char c) {
individualLetter[c]--;
if (individualLetter[c] == 0)
--necessaryMatches;
}
inline bool allLettersMatched() { return necessaryMatches == 0; }
};
std::string solution(std::string toBeSearchedIn, std::string toBeSearchedFor) {
// Counter with somespecial properties
SpecialCounter counter(toBeSearchedFor);
// This will be slided. End of window is always last read character. Start of window may increase
Index currentWindowStart {};
// The potential solution
Index resultingWindowStart {};
Index resultingWindowWith{ std::numeric_limits<size_t>::max() };
// Iterate over all characters of the string under evaluation
for (Index index{}; index < toBeSearchedIn.length(); ++index) {
// We saw a character. So, subtract from characters to be searched
counter.decrementFor(toBeSearchedIn[index]);
// If we hit and found all necessary characters and adjusted the sliding windows start position
while (counter.allLettersMatched()) {
// Calculate start and width of sliding window. So, if we found a new, more narrow window
const unsigned int currentWindowWith{ index - currentWindowStart + 1 };
if (currentWindowWith < resultingWindowWith) {
// Remember one potential solution
resultingWindowWith = currentWindowWith;
resultingWindowStart = currentWindowStart;
}
// Now, for the sliding window. We saw and decremented thsi character before
// Now we see it in the sliding window and increment it again.
counter.incrementFor(toBeSearchedIn[currentWindowStart]);
// Slide start of window to one to the right
currentWindowStart++;
}
}
return (resultingWindowWith != std::numeric_limits<size_t>::max()) ? toBeSearchedIn.substr(resultingWindowStart, resultingWindowWith) : "No solution";
}
int main()
{
const std::string toBeSearchedIn{ "KKKADOBECODEBBBAANCKKK" };
const std::string toBeSearchedFor = { "AABBC" };
std::cout << "Solution:\n" << solution(toBeSearchedIn, toBeSearchedFor) << '\n';
}