最短摘要问题
Alibaba笔试题:给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;再给定N个英文单词关键字,请说明思路并编程实现方法 String extractSummary(String description,String[] keywords)
目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。。
类似的
那么,这段摘要是怎么产生的呢?简化一:
- 假设给定的已经是经过网页分词之后的结果,词语序列数组为W。其中W[0], W[1],…, W[N]为一些已经分好的词语。
- 假设用户输入的搜索关键词为数组Q。其中Q[0], Q[1],…, Q[m]为所有输入的搜索关键词。
这样,生成的最短摘要实际上就是一串相互联系的分词序列。比如从W[i]到W[j],其中,0<i<j<=N。例如上图所示的摘要“程序员面试、算法研究、编程艺术、红黑树4大经典原创吸了集锦与总结 作者:July--结构之法算法之道blog之博主.....”中包含了关键字——“结构之法”。
OK,如何解决这样的问题呢?有没有感觉一头雾水?先看一个例子,碰到问题不要着急,可以举一些例子来找思路。
分词数组W:w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1; 关键字数组Q:qo,q1;
最短摘要数组Seq;
此时最短摘要应该是q0,w9,q1; 我们是如何得到的呢? 第一次遍历找到包含全部关键字的摘要得到w0,w1,w2,w3,q0,w4,w5,q1;更新Seq;
第二次遍历找到包含全部关键字的摘要得到w1,w2,w3,q0,w4,w5,q1;更新Seq;
…… 继续遍历找到包含全部关键字的摘要得到q0,w4,w5,q1;更新Seq;
……
继续遍历找到包含全部关键字的摘要得到w5,q1,w6,w7,w8,q0;更新Seq;
继续遍历找到包含全部关键字的摘要得到q1,w6,w7,w8,q0;更新Seq;
继续遍历找到包含全部关键字的摘要得到w6,w7,w8,q0,w9,q1;;更新Seq;
…… 最后一次遍历找到包含全部关键字的摘要得到q0,w9,q1;更新Seq;
明白了倒是明白了,可是这复杂度是不是很高?
总是重复地循环,好多都是不必要的无用功。 我们可以降低复杂度吗? 我们可以这么做:
第一次遍历找到包含全部关键字的摘要得到w0,w1,w2,w3,q0,w4,w5,q1;更新Seq;
此时不扫描遍历了,更新begin指针,使之向前。直到得到q0,w4,w5,q1;更新Seq;
begin继续向前导致w4,w5,q1,不满足。此时需要end指针向后遍历。
继续遍历找到包含全部关键字的摘要得到w4,w5,q1,w6,w7,w8,q0;更新Seq;
此时不扫描遍历了,更新begin指针,使之向前。直到得到q1,w6,w7,w8,q0;更新Seq;
begin继续向前导致w6,w7,w8,q0,不满足。此时需要end指针向后遍历。
第三次遍历找到包含全部关键字的摘要得到w6,w7,w8,q0,w9,q1;更新Seq;
此时不扫描遍历了,更新begin指针,使之向前。直到得到q0,w9,q1;更新Seq;
begin继续向前导致w9,q1,不满足。此时需要end指针向后遍历。知道end=N;
每次遍历结束后我们不从下一个分词开始,我们从得到的包含数组的第一个关键字的后面开始遍历。这样能省很多劲呢。
在遍历的过程当中我们需要保存最短摘要的长度,和起始位置。
编码实现
bool IsAllContain(int key[],int N,int title[],int begin,int end);
//判断begin-end是否包含全部关键字
int main()
{
int key[N]={14,17};
int title[M]={0,1,2,3,14,5,6,17,8,9,10,11,17,13,14};
int TargetLen=M+1; //初始化摘要长度大于文章的最长长度
int begin=0; //扫描指针
int end=0; //扫描指针
int AbstractBegin=0; // 目标摘要的起始地址
int AbstractEnd=0; // 目标摘要的结束地址
while(true)
{
while((0==IsAllContain(key,N,title,begin,end))&&end<M)
{ //如果不包含全部关键字继续使end向前
end++;
}
while(IsAllContain(key,N,title,begin,end))
{ //如果包含全部关键字继续使begin向前,同时更新最短摘要
if(end-begin+1<TargetLen)
{
TargetLen=end-begin+1;
AbstractBegin=begin;
AbstractEnd=end;
}
begin++;
}
if(end>=M)
break;
}
cout<<"AbstractBegin="<<AbstractBegin<<" AbstractEnd="<<AbstractEnd<<endl;
cout<<"TargetLen="<<TargetLen<<endl;
return 0;
}
bool IsAllContain(int key[],int N,int title[],int begin,int end) //判断begin-end是否包含全部关键字
{
int num=0; //只能是双重循环,因为key与title的不是一致顺序
for(int i=0;i<N;i++)
{
for(int j=begin;j<=end;j++)
{
if(title[j]==key[i])
{
num++; //每一次循环得到一个不同的关键字足够了
break;
}
}
}
if(num==N)
return true;
else
return false;
}
算法明白了之后,代码就很好懂了。
时间复杂度不敢说很好了,但是比那个复杂度好好很多了。。
转载请注明出处http://blog.csdn.net/sustliangbo/article/details/9277485