来源:http://www.cnblogs.com/pangxiaodong/archive/2011/09/08/2171551.html
http://www.cnblogs.com/jack204/archive/2012/09/03/2668667.html
编程之美--最短摘要生成
题目:
Alibaba笔试题:给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;
再给定N个英文单词关键 字,请说明思路并编程实现方法String extractSummary(String description,String[] key words),
目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。(不限编程语言)20分。
先来看看这些序列:
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
分析
当初看这道题时,看了好了几遍都没看懂。后来总算弄明白:给出的字符串是用其它程序分好词的,关键字符串也是用其它程序分好词的,而不是按用户直接输入的字符串。比如书上给的例子:“微软亚洲研究院 使命”,不是按空格分成两个关键词,“微软亚洲研究院”和“使命”,而是按其它程序分成:“微软”、“亚洲”、“研究院”和“使命”四个关键词。
“最短摘要”应该是指:包含所有关键字(关键字不要求按用户输入的顺序排列)的长度最短的摘要。书上的解法,把“最短摘要”理解成包含所有关键字且词个数最少的摘要。
这个过程要记录最短文摘的信息。
这个时间复杂度是 O(N ^ 2 * M)
N 是文档的长度
M 是关键词数组的大小
总结的滑动窗口法是:
------------------------------------------------------
使用滑动窗口的办法,找出最短摘要。我们把这个滑动窗口叫做摘要滑动窗口。
摘要滑动窗口左边界L,右边界R。
窗口中应该维护的信息:
- 窗口中已经遍历过的关键字序列----可使用队列才存储;
- 窗口中各个关键字出现的个数----可使用hashtable来表示或者数组也行。
[while]
右边界R向右移动的原则:
当前窗口中不包含所有种类的关键字,R向右移动寻找更新的关键字。
左边界L向右移动的原则:
当前窗口中已经包含了所有种类的关键字,计算当前摘要长度,并从队列中拿出一个关键字,即L向右移动一个关键字;
L与R一直移动下去,一直到R不能往右移动时候,循环结束。
[end while]
代码实现:
1. 简述
这道题的题干说的不是特别清楚,在网上看了几篇相关的博文才搞清楚,对于算法本身就是编程之美给出的解法,现在我还没有深入的理解。题干大意如下:输入两个字符串,一个表示用户输入的查询,另一个表示一篇文档的内容。对于查询和文档分别进行自动分词后,用户查询和文档内容的两个字符串变为两个词语序列。比如,下面的keyword表示分词后的用户查询,str表示分词后的文档内容。
string str[] = {
" 微软 " , " 亚洲 " , " 研究院 " , " 成立 " , " 于 " , " 1998 " , " 年 " , " , " , " 我们 " , " 的 " , " 使命 " ,
" 是 " , " 使 " , " 未来 " , " 的 " , " 计算机 " , " 能够 " , " 看 " , " 、 " , " 听 " , " 、 " , " 学 " , " , " ,
" 能 " , " 用 " , " 自然语言 " , " 与 " , " 人类 " , " 进行 " , " 交流 " , " 。 " , " 在 " , " 此 " , " 基础 " , " 上 " ,
" , " , " 微软 " , " 亚洲 " , " 研究院 " , " 还 " , " 将 " , " 促进 " , " 计算机 " , " 在 " , " 亚太 " , " 地区 " ,
" 的 " , " 普及 " , " , " , " 改善 " , " 亚太 " , " 用户 " , " 的 " , " 计算 " , " 体验 " , " 。 " , " ” "
};
所要求得是str1中的若干个连续的字符串,假设为str[i]-str[j],其中包含keyword中的所有词语,且str[i]-str[j]是满足这个条件的最短的一个。
2. 思路
这个主要注意几点:首先,str[i]-str[j]包含keyword里面的所有词语,但是不要求顺序相同,然后,str[i]-str[j]是所有满足这样要求的最短子串。
主要方法是:deque<index> store: 记录当前摘要的所有单词在str中的下标,map<string, int> record记录所有当前摘要中出现的次数。min_len:当前最短摘要的长度。min_index_first:当前最短摘要的第一个词语在str中的下标,min_index_last:当前最短摘要的最后一个词语在str中的下标。
第一步,寻找第一个完整摘要,更新store和record。如果没找到,程序结束。如果找到了计算min_len,min_index_first,min_index_last;,然后进行第二步。
第二步,队列中去掉一个第一个关键词,同时更新record,如果record中该关键词计数还大于0,这说明虽然当前摘要短了,但是还是完整的,因此这必然是一个更短的摘要,min_len--;min_index_first++; 重复第二步。 如果record中关键词的计数为0了,这说明当前摘要不完整了,需要向后面找缺少的关键词,进入第三步。
第三步,在当前摘要后面寻找缺少的关键词。如果下标越界了,说明不能再找到完整的摘要了,停止工作即可。如果找到的不是关键词,index++,即向后移动继续找。如果找到的是关键词更新store和record,此时如果找到的关键词刚好还是缺少的关键词,那么转向第二步,无论与否,记得index++先。
3. 代码实现
大体思路不难,但是把当前摘要的关键词下标序列,当前摘要的关键词计数,最短摘要位置,缺少的关键词,这些数据放在一起,还要更新,逻辑就有点复杂了。写了好一会才写出来,不好说对与不对,毕竟没有完整的测试。
#include < string >
#include < map >
#include < deque >
using namespace std;
void find_min_len_abstract( string str[], string keyword[], int len_str, int len_keyword) {
// 初始化map
map < string , int > record;
for ( int i = 0 ; i < len_keyword; i ++ ) {
record[keyword[i]] = 0 ;
}
// 匹配过程
deque < int > store; // 存储的是str中关键词的下标
int min_len = 0 ;
int min_index_first = - 1 ;
int min_index_last = - 1 ;
int find_key_num = 0 ;
int index = 0 ;
while (find_key_num < len_keyword && index < len_str) {
if (record.find(str[index]) == record.end()) { // str[index]不是关键字
index ++ ;
}
else { // str[index]是关键字
if (record[str[index]] == 0 ) // 第一次找到这个关键字
find_key_num ++ ;
record[str[index]] = record[str[index]] + 1 ; // 计数加1
store.push_back(index);
index ++ ;
}
}
if (find_key_num < len_keyword) { // 一个满足的摘要都没找到
cout << " not abstract found " << endl;
}
else { // 找到一个了,试着找找更好的
min_len = store.back() - store.front() + 1 ;
min_index_first = store.front();
min_index_last = store.back();
// 第一个摘要
cout << " 第一个摘要 " << endl;
cout << " min len: " << min_len << endl;
for ( int i = min_index_first; i <= min_index_last; i ++ )
cout << str[i] << " " ;
cout << endl;
cout << " --------------------------------------------- " << endl;
string need_key;
bool already_found = true ;
while ( true ) {
if (already_found == true ) { // 刚好找到一个新摘要
string first_key = str[store.front()];
record[first_key] -- ; // 减少当前最前面的关键词
store.pop_front(); // 关键词出队
if (record[first_key] == 0 ) { // 如果该关键词没了
already_found = false ;
need_key = first_key; // 记录需要寻找的关键词
}
else { // 少了词语,但是还包含所有关键词,说明这是一个更短的摘要
min_len -- ;
min_index_first ++ ;
cout << " 更短的摘要 " << endl;
cout << " min len: " << min_len << endl;
for ( int i = min_index_first; i <= min_index_last; i ++ )
cout << str[i] << " " ;
cout << endl;
cout << " --------------------------------------------- " << endl;
}
}
else { // 需要向后面找满足条件的关键词
if (index >= len_str) { // 不可能找到需要的关键词了
break ;
}
else if (record.find(str[index]) == record.end()) { // 不是关键词
index ++ ;
}
else { // 是关键词
record[str[index]] = record[str[index]] + 1 ;
store.push_back(index);
if (str[index] == need_key) { // 正好还是需要找到的关键词
already_found = true ;
if ((store.back() - store.front() + 1 ) < min_len) { // 新的摘要更短
min_len = store.back() - store.front() + 1 ;
min_index_first = store.front();
min_index_last = store.back();
// 更短的摘要
cout << " 更短的摘要 " << endl;
cout << " min len: " << min_len << endl;
for ( int i = min_index_first; i <= min_index_last; i ++ )
cout << str[i] << " " ;
cout << endl;
cout << " --------------------------------------------- " << endl;
}
else {
cout << " 并非更短的摘要 " << endl;
cout << " min len: " << store.back() - store.front() + 1 << endl;
for ( int i = store.front(); i <= store.back(); i ++ )
cout << str[i] << " " ;
cout << endl;
cout << " --------------------------------------------- " << endl;
}
}
index ++ ;
} // else
} // else
} // while
} // else
// 输出结果
}
int main() {
// string keyword[] = { "微软", "计算机", "亚洲", "中国"};
string keyword[] = { " 微软 " , " 计算机 " , " 亚洲 " };
string str[] = {
" 微软 " , " 亚洲 " , " 研究院 " , " 成立 " , " 于 " , " 1998 " , " 年 " , " , " , " 我们 " , " 的 " , " 使命 " ,
" 是 " , " 使 " , " 未来 " , " 的 " , " 计算机 " , " 能够 " , " 看 " , " 、 " , " 听 " , " 、 " , " 学 " , " , " ,
" 能 " , " 用 " , " 自然语言 " , " 与 " , " 人类 " , " 进行 " , " 交流 " , " 。 " , " 在 " , " 此 " , " 基础 " , " 上 " ,
" , " , " 微软 " , " 亚洲 " , " 研究院 " , " 还 " , " 将 " , " 促进 " , " 计算机 " , " 在 " , " 亚太 " , " 地区 " ,
" 的 " , " 普及 " , " , " , " 改善 " , " 亚太 " , " 用户 " , " 的 " , " 计算 " , " 体验 " , " 。 " , " ” "
};
int len_keyword = sizeof (keyword) / sizeof ( string );
int len_str = sizeof (str) / sizeof ( string );
find_min_len_abstract(str, keyword, len_str, len_keyword);
system( " PAUSE " );
return 0 ;
}
输出结果如下:
4. 参考
最短摘要的生成 http://www.cnblogs.com/flyinghearts/archive/2011/03/24/1994453.html