题目:抽象点说,就是在一个字符串中,找一些目标字符串,找到包含所有目标字符串的最小字符串。题目虽然叫做最短摘要生成,但和实际的搜索snippet的计算还是有比较大的差距的。
先来看看这些序列:
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
问题在于,如何一次把所有的关键词都扫描到,并且不遗漏。扫描肯定是无法避免的,但是如何把两次扫描的结果联系起来呢?这是一个值得考虑的问题。
沿用前面的扫描方法,再来看看。第一次扫描的时候,假设需要包含所有的关键词,从第一个位置w0处将扫描到w6处:
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
那么,下次扫描应该怎么办呢?先把第一个被扫描的位置挪到q0处。
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
然后把第一个被扫描的位置继续往后面移动一格,这样包含的序列中将减少了关键词q0。那么,我们便可以把第二个扫描位置往后移,这样就可以找到下一个包含所有关键词的序列。即从w4扫描到w9处,便包含了q1,q0:
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
这说明一个什么问题?说明只要是包含给定的串的所有词语就可以了,而不一定保持要找的串中词语前后顺序
这样,问题就和第一次扫描时碰到的情况一样了。依次扫描下去,在w中找出所有包含q的序列,并且找出其中的最小值,就可得到最终的结果。
编程之美上给出了如下参考代码:
为什么设置目标长度为总长度+1?因为可能页面从0下标开始到该页面最后一个下标结束才含有要找的目的串的所有字符
int nTargetLen = N + 1; // 设置目标长度为总长度+1
int pBegin = 0; // 初始指针
int pEnd = 0; // 结束指针
int nLen = N; // 目标数组的长度为N
int nAbstractBegin = 0; // 目标摘要的起始地址
int nAbstractEnd = 0; // 目标摘要的结束地址
while(true)
{
// 假设未包含所有的关键词,并且后面的指针没有越界,往后移动指针
while(!isAllExisted() && pEnd < nLen)
{
pEnd++;
}
// 假设找到一段包含所有关键词信息的字符串
while(isAllExisted())
{
if(pEnd – pBegin < nTargetLen)
{
nTargetLen = pEnd – pBegin;
nAbstractBegin = pBegin;
nAbstractEnd = pEnd – 1;
}
pBegin++;
}
if(pEnd >= N)
Break;
}
解法:文献[1]给出了一种比较好的解法,策略还是使用双指针,双指针对于很多算法设计很有价值,算法的思想是采用两个指针,开始两个指针都指向缓冲区的头部,尾指针向后扫描,直到头指针和尾指针中间包含了全部的关键字,那么头指针向后移动,直到包含全部关键字这个条件失败,这时截取字串并和已取得的最小字串比较,如果小则替换。头指针、尾指针都向后一个位置(这点很重要,开始就忘记了移动头指针,导致程序出错),继续扫描。另外,由于一个关键字可能重复多次,因此在判断是否包含全部关键字时要采用计数机制,才能保证查看的准确。这样只要头指针和尾指针扫描两次字符串就可以完成生成算法。
具体代码如下:
#include <stdio.h>
#include <string>
#include <map>
class KeyWordChecker {
public:
KeyWordChecker() : current_state_(false) {}
void AddKeyWord(const std::string& word) {
if (keywords_.find(word) == keywords_.end()) {
keywords_[word] = 0;
}
}
bool IsContainAllKeyWord(const std::string& word, bool add) {
if (keywords_.find(word) == keywords_.end()) {
return current_state_;
}
if (add) {
keywords_[word]++;
} else {
keywords_[word]--;
}
std::map<std::string, int>::iterator begin = keywords_.begin();
int counter = 0;
while (begin != keywords_.end()) {
if (begin->second > 0) {
counter++;
} else {
break;
}
begin++;
}
if (counter == keywords_.size()) {
current_state_ = true;
return true;
} else {
current_state_ = false;
return false;
}
}
private:
std::map<std::string, int> keywords_;
bool current_state_;
};
std::string GenerateSnippet(const std::string& content, KeyWordChecker* keyword_checker) {
int begin = 0;
int end = 0;
std::string snippet;
int min_length = content.size();
while (end < content.size()) {
if (!keyword_checker->IsContainAllKeyWord(std::string(1, content[end]), true)) {
end++;
continue;
}
while (begin <= end && keyword_checker->IsContainAllKeyWord(std::string(1, content[begin]), false)) {
begin++;
}
if (end - begin + 1 < min_length) {
snippet = content.substr(begin, end - begin + 1);
min_length = end - begin + 1;
}
end++;
begin++;
}
return snippet;
}
int main(int argc, char** argv) {
std::string content = "abbbbbcaaadebmmmmdcfg";
KeyWordChecker keyword_checker;
keyword_checker.AddKeyWord("b");
keyword_checker.AddKeyWord("d");
std::string snippet = GenerateSnippet(content, &keyword_checker);
printf("snippet:%s\n", snippet.c_str());
}
变型题:
百度笔试:n个颜色的结点组成的圈,总个数为m,求包含n个结点最短的子链。
程序实现
#include <iostream>
#include <string.h>
using namespace std;
#define CHARNUM 256
struct pos{
int s;
int e;
};
pos abstract(char a[],int m,int n)
{
pos p={0,0};
int r[CHARNUM]={0};
int sum(0),i(0),j(0);
int min = m;
while(j<m)
{
if(r[a[j]]++ == 0)
sum++;
if(sum == n)
{
while(r[a[i]]>1 && i<j)
r[a[i++]]--;
if(min>j-i)
{
min = j-i;
p.s = i;
p.e = j;
}
}
j++;
}
return p;
}
int main()
{
char s[]="adcaccdddaacbbdabdbcdc";
int i = 0;
pos p = abstract(s,strlen(s),4);
for(i=0;i<strlen(s);i++)
cout<<s[i];
cout<<endl;
for(i=0;i<strlen(s);i++)
if(i>=p.s && i<=p.e)
cout<<s[i];
else
cout<<" ";
cout<<endl;
cout<<"s:"<<p.s<<";e:"<<p.e<<endl;
}