第五章 字串專題 ---------------- 5.11 題解:最短摘要的生成

Curtis_發表於2019-03-19

題目:

Alibaba筆試題,給定一段產品的英文描述,包含M個英文單詞,每個英文單詞以空格分隔,無其他標點符號;再給定N個英文單詞關鍵字,請說明思路並程式設計實現方法。String extractSummary(String description,String[] key words)目標是找出此產品描述中包含N個關鍵字(每個關鍵詞至少出現一次)的長度最短的子串,作為產品簡介輸出。(不限程式語言)20分。

思路一:

直接暴力解法,迴圈暴力破解,每次判斷關鍵字是否全部包含,然後不斷更新邊界。

思路二:

先來看看這些序列(w為英文單詞,q為關鍵字):

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

問題在於,一次性掃描確定邊界這不太現實,肯定要多次掃描,不斷更新邊界,這裡只說每個關鍵字必須要出現一次以上,並沒有說要順序出現。先第一次掃描,掃描到第一個關鍵字,從第一個位置w0處將掃描到q0處:

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

然後掃描的位置繼續往後移動,移動到q1,這樣包含的序列中就少了q0,那麼就從q1開始掃描,這樣就可以找到下一個包含所有關鍵字的序列,即從q1掃描到了q0處,這兒也要更新邊界:

w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1

然後,在繼續往後掃描,定位到下一個關鍵字q0,繼續往後掃描,如此迴圈往後掃描,直至掃描完成,那麼邊界長度就是最小的了。

程式碼:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class 最短摘要 {

    public static void main(String[] args) {
         solve1(new String[]{"a", "b", "c", "seed", "h", "e", "f", "c", "c", "seed", "e", "f", "seed", "c"}, 
                 new String[]{"c", "e"});
         solve2(new String[]{"a", "b", "c", "seed", "c", "e", "f", "c", "c", "seed", "e", "f", "seed", "c"}, 
                 new String[]{"c", "c", "e", "f"});
         solve2(new String[]{"a", "b", "a", "a", "b", "c", "d", "h", "e", "f", "f"}, new String[]{"b", "c", "d"});
    }

    // 暴力破解法
    public static void solve1(String[] w, String[] q) {
        int length = Integer.MAX_VALUE;
        int begin = -1;
        int end = -1;
        for (int i = 0; i < w.length; i++) {
            // 求以i開頭包含所有關鍵字的序列
            for (int j = i+1; j < w.length; j++) {
                // 如果全部關鍵字已經再seq中。
                if (containsAll(q,w,i,j)) {
                    // 判斷當前這個序列是不是較短的序列
                    // System.out.println(seq);
                    if (j-i+1<length) {
                        length = j-i+1;
                        begin = i;
                        end = j;
                    }
                    break;
                }
            }
        }
        print(w,begin,end);
    }
    
    /**
     * 這種解法解決了keys關鍵字中有重複的現象
     * @param w
     * @param keys
     */
    public static void solve2(String[] w, String[] keys) {
        Arrays.sort(keys);
        // begin和end用於在找到更短的包含全部關鍵字的子陣列時更新
        int begin = -1;
        int end = -1;
        
        int j = -1;  // 上一次囊括了所有關鍵字的右邊界
        
        int minLen = Integer.MAX_VALUE;
        for (int i = 0; i < w.length; i++) {
            // 如果i位置是關鍵字,求以i開頭包含所有關鍵字的序列
            String word1 = w[i];
            int index = Arrays.binarySearch(keys, word1);
            if (-1==index) {
                continue;
            }else {  // i是一個關鍵字
                // 如果已經全部找到
                if (j<w.length&&j>=i&&containsAll(keys, w, i, j)) {
                    if (j-i+1<minLen) {  // 更新
                        minLen = j-i+1;
                        begin = i;
                        end = j;
                    }
                    continue;
                }
            }
            
            if (j==-1) {
                j = j+1;   // j值初始化
            }
            while(j<w.length){
                String word2 = w[j];  // 文章單詞
                int index1 = Arrays.binarySearch(keys, word2);
                if (-1==index1) {
                    j++;
                    continue;
                }else {  // 找到關鍵字  這裡應該是第一次掃描的包含所有關鍵字的  然後繼續for迴圈不斷更新邊界
                    if (containsAll(keys, w, i, j)) { //全部到齊  
                        if (j - i + 1 < minLen) {// 更新
                            minLen = j - i + 1;
                            begin = i;
                            end = j;
                        }
                        break;
                    }else {
                        j++;
                    }
                }
            }
        }
        print(w, begin, end);
    }

    private static boolean containsAll(String[] keywords, String[] w, int i, int j) {
        Map<String, Integer> map = new HashMap<>();
        for (int k = 0; k < keywords.length; k++) {
            String key = keywords[k];
            if (map.get(key)==null) {
                map.put(key, 1);
            }else {
                map.put(key, map.get(key)+1);
            }
        }
        
        Map<String, Integer> map2 = new HashMap<>();
        for (int k = i; k <= j; k++) {
            String key = w[k];
            if (map2.get(key)==null) {
                map2.put(key, 1);
            }else {
                map2.put(key, map2.get(key)+1);
            }
        }
        for(Map.Entry<String, Integer> e:map.entrySet()){
            if (map2.get(e.getKey())==null||map2.get(e.getKey())<e.getValue()) {
                return false;
            }
        }
        return true;
    }

    private static void print(String[] w, int begin, int end) {
        System.out.println(begin+" "+end);
        for (int i = begin; i <=end; i++) {
            System.out.print(w[i]+" ");
        }
        System.out.println();
    }

    

}

結果:

 

C++程式碼:(沒除錯成功=。=) 

#include<iostream>
#include<algorithm>
#include<climits>
#include<map>
using namespace std;

template<class T>
int length(T& arr)
{
	return sizeof(arr)/sizeof(arr[0]);
}

bool containAll(string keywords[],string w[],int i,int j,int kl)
{
	map<string,int> mp;
	//len儲存keywords長度,避免直接用於for迴圈時,多次呼叫,複雜度提高 
	int len=kl;
	for(int k=0;k<len;k++)
	{
		string key=keywords[k];
		if(mp.find(key)==mp.end())
		{
			mp[key]=1;
		}
		else
		{
			mp[key]++;
		}
	}
	map<string,int> mp2;
	for(int k=i;k<=j;k++)
	{
		string key=w[k];
		if(mp2.find(key)==mp2.end())
		{
			mp2[key]=1;
		}
		else
		{
			mp2[key]+=1;			
		}
	}
	
	for(map<string,int>::iterator it=mp.begin();it!=mp.end();it++)
	{		
		if(mp2.find(it->first)==mp2.end()||mp2[it->first]<mp[it->first])
		{
			return false;
		}
	}
	//對於mp中的所有元素,在mp2中都能找到,且value值不少於mp中的value值,則返回true 
	return true;	
}

int strSearch(string s[],string str,int kl)
{
	int l=kl;
	for(int i=0;i<l;i++)
	{
		if(s[i]==str) return 1;
	}
	return 0;
}


//w:要檢索的字串陣列; key:要找到關鍵字 
void solve2(string w[],string keys[],int kl,int wl)
{
	int kLen=kl;
	int wLen=wl;
	
	sort(keys,keys+kLen);
	//begin和end用於在找到更短的包含全部關鍵字的子陣列時更新
	int begin=-1;
	int end=-1;
	
	int j=-1;//上一次囊括了所有關鍵字的右邊界
	
	int minLen=INT_MAX;
	
	for(int i=0;i<wLen;i++)
	{
		// 如果i位置是關鍵字,求以i開頭包含所有關鍵字的序列
		string word1=w[i];
		//如果在keys中沒找到w[i],即,w[i]不是關鍵字,則continue 
		if(!strSearch(keys,word1,kl))
		{
			continue; 
		}
		else
		{
			//i是一個關鍵字 
			//如果已經全部找到 
			if(j<wLen&&j>=i&&containAll(keys,w,i,j,kl))
			{
				if(j-i+1<minLen)
				{   //更新 
					minLen=j-i+1;
					begin=i;
					end=j;
				}
				continue;
			}
		}
		if(j==-1)
		{
			j=i+1;//j值初始化 
		}
		while(j<wLen)
		{
			string word2=w[j];
			//word2不是關鍵字 
			if(!strSearch(keys,word2,kl))
			{
				j++;
				continue;
			}
			// word2是關鍵字 
			else
			{
				//全部到齊 
				if(containAll(keys,w,i,j,kl))
				{
					if(j-i+1<minLen)
					{
						minLen=j-i+1;
						begin=i;
						end=j;
					}
					break;
				}
				else
				{
					j++;					
				}
			}
		} 
		
	}
	
	//輸出最後結果
	for(int i=begin;i<=end;i++)
	{
		cout<<w[i]<<" ";
	} 
	
} 


int main()
{
	string ww[]={"a", "b", "c", "seed", "h", "e", "f", "c", "c", "seed", "e", "f", "seed", "c"};
	string ky[]={"c", "e"};
	int wl=length(ww);
	int kl=length(ky);
	cout<<wl;
	cout<<kl;
	
	solve2(ww,ky,wl,kl);
	
    return 0;
}

 

相關文章