Alibaba笔试题:给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;再给定N个英文单词关键字,请说明思路并编程实现方法String extractSummary(String description,String[] key words),目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。(不限编程语言)20分。
这道笔试题和编程之美最短摘要生成的方法类似,我用java实现了这个方法。
先来看看这些序列:
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的序列,并且找出其中的最小值,就可得到最终的结果。
编程之美上给出了如下参考代码:
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; }
以下是我写的java实现代码。
使用HashMap将关键字映射到数字,减少字符串比较,能进一步提高效率。使用数组保存关键字被访问到的次数,用来判断是否包含全部关键字。
package com.flyoung; import java.util.HashMap; import java.util.Map; public class Abstract { /* * 编程之美 最短摘要生成 * */ private int[] keywordsArray; //记录关键字被访问次数的数组 private int pBegin=0;//查找起始点 private int pEnd=0;//查找终点 private int abstractBegin=0;//摘要起始点 private int abstractEnd=0;//摘要终点 private int targetLen;//摘要最小长度 private Map<String,Integer> map;//将关键字映射成数字 public Abstract(String[] keywords){ int len = keywords.length; this.keywordsArray = new int[len]; this.map = keywordsMap(keywords); } public String extractSummary(String description,String[] keywords){ String[] array = description.split(" ");//将字符串转化为数组 return extract(array,keywords); } //实际的抽取函数 public String extract(String[] description,String[] keywords){ String summary = ""; int nLen = description.length; targetLen = nLen+1; while(true){ while(!isAllExisted()&&pEnd<nLen){ if(this.map.get(description[pEnd])!=null){ //如果pEnd所指向的字符在keywords中 setKeywordsArray(keywordsArray,this.map.get(description[pEnd]),0);//该字符相应的统计个数 + 1 } pEnd++; } while(isAllExisted()){ if(pEnd-pBegin<targetLen){ targetLen = pEnd-pBegin; abstractBegin = pBegin; abstractEnd = pEnd-1; } if(map.get(description[pBegin])!=null){ //如果pBegin所指向的字符在keywords中 setKeywordsArray(keywordsArray,map.get(description[pBegin]),1); //该字符相应的统计个数 - 1 } pBegin++; } if(pEnd>=nLen){ break; } } for(int j=abstractBegin;j<=abstractEnd;j++){ //把包含keyword的最短 description 组合起来成summary if(j!=abstractEnd){ summary = summary+description[j]+" "; }else{ summary +=description[j]; } } return summary; } public Map<String,Integer> keywordsMap(String[] keywords){ //建立从keyword到integer的hash映射 Map<String,Integer> map = new HashMap<String,Integer>(); int len = keywords.length; for(int i=0;i<len;i++){ map.put(keywords[i], i); } return map; } //设置关键字被访问到的次数 public void setKeywordsArray(int[] keywordsArray,int i,int flag){ //flag:0 add flag:1 sub if(flag==0){ keywordsArray[i]++; }else{ keywordsArray[i]--; } } //检查是否包含全部关键字 public boolean isAllExisted(){ boolean result = true; for(int a:keywordsArray){ if(a==0){ result=false; break; } } return result; } public static void main(String[] args) { String description="hello software hello test world spring sun flower hello"; String[] keywords = {"hello","world"}; Abstract nAbstract = new Abstract(keywords); System.out.println(nAbstract.extractSummary(description, keywords)); } }