包含給定字符集的最小子串

2021-06-18 19:46:07 字數 3943 閱讀 3748

給定乙個字元集合must

[0,...,m-1

] 和乙個字串str

[0,...,n-1

]。假定n

<=m

。找出str

中包含must

中所有字元的最短子串。

例如:給乙個字串s1,和乙個小串s2,求演算法能在s1中找到包含s2裡所有字元的最小子串。比如:

s1 = 「adobecodebanc」

s2 = 「abc」 

最小子串是 「banc」,要求o(n)的演算法。

1、最直接和簡單的演算法當然是暴力搜尋(brute-force search):

minimal := str [0,...,n-1 ]

fori from 0 ton-1 do

search smallest ki

such that str [i,...,ki

]contains all characters from must .

//尋找最小的ki

,使得str [i,...,ki

] 包括所有must 中的字元

if str [i,...ki

] is shorter than minimal , then update minimal to str [i,...,ki

]//如果該子串比當前已知的最知子串還短,則設定當前最小子串為該子串

可以看出,該演算法的複雜度為 o(nk ) 。如果 k =o(n ),則為o(n2

)。那麼,有沒有更好的演算法呢?一般來說,暴力演算法會包含很多不必要的或重複的計算。這些不必要的計算有時可以通過剪枝的方法來避免。接下來,我們就來研究如何避免不必要的計算。

首先,如果 str [i ] 不在must 中,則可以安全的跳過以 str [i ] 開始的子串(注意:這裡所說的以 str [i ] 開始的子串,是指從 str 的位置 i 開始的子串,即 str [i,...,k ],而不是以 str [i ] 這個字元開頭的子串;下同。)這是基於如下觀察:

觀察1: 如果 str [i,...,ki

] 是最短的包含 must 中所有字元的子串,則 str [i ] 和str [ki

] 都在

must

中。其次,我們還有

觀察2: 如果 str [i,...,ki

] 是包含 must 的子串,並且str [i ] 在 str [i,...,ki

] 中不止出現一次,

str [i+1,...,ki

] 也包含 must 中的所有字元。

基於觀察2,在暴力演算法中,當然從 

i轉到 

i+1時,如果能以某種快速的方法知道

str

[i ] 在 str [i,...,ki

] 中不只出現一次,則無須搜尋以 

str[

i+1] 開始的符合要求的子串:因為 

str[

i+1,...,ki

] 就是我們要尋找的。

觀察3:

如果 str [i,...,ki

] 是包含 must 的子串,但 str [i ] 只在 str [i,...,ki

] 出現一次,顯然,str [i+1,...,ki

] 包含了除 str [i ]外 must 中所有的字元。如果我們在 str [ki +1,..., n-1 ] 中尋找第乙個出現的 str [i ],記為 ki+1

,則str

[i+1,...,ki+1

] 是以 

str[

i+1] 開頭的包含

must

的最短子串。

基於這以上三個觀察,我們可以減少暴力演算法中的多少不必要的計算呢?以下我們先給出

剪枝後的演算法。

step 1: 首先,從i := 0 ,我們找到第乙個包含must 的以str [0 ] 開始的最小子串:str [0,...,k0

]。顯    然,這也是當前我們所知的最短子串。設 j := k0

;step 2: 接下來,假定我們已經知道str [i,...,j ] 包含了 must 。

step 3: 考慮當前 i 。

1) 如果 str [i ] 不在 must 中,根據觀察1,我們繼續向前移動i := i + 1 ;

2) 如果str [i ] 在 must 中,且在 str [i ] 在 str [i,.j ] 中不止出現一次,根據觀察2,繼續向前移動i= i + 1 ;

3) 否則,如果 str [i,...,j ] 比 mininal 還要短,則我們找到了更短的子串,設定mininal := str [i,...,j ],然後向前移動 j 直到 str [j ] == str [i ] 或者j 

< n-1 ,則結束程式;否則,回到 step 3。

注意到我們在 step 3 中的 1)和 2)並沒有更新 mininal 。這是因為那是多餘的。如果發生了1),則在之後的某一刻一定會發生2)或者3)(這裡假定 must 不為空);如果發生了2),則在之後的某一刻會發生3)。而由於基於以上三個觀察的剪枝是安全的,即不會影響到解的正確性,所以,上述演算法也是正確的。事實上,該演算法的正確性也可以通過歸納法得以證明。上面的描述基本上就是遵從了歸納法的描述框架。

那麼,剪枝後的演算法複雜度呢?注意到 step 3 的迴圈中,我們每次都至少向前移動 i 或 j 一步,因此,該迴圈最多執行 2n 次。如果我們使用乙個雜湊表來記錄 str [i,...,j ] 中每個在 must 中的字元的出現次數,就可以在o(1)的時間內決定是分支到1)2)3)中哪一支。而顯然,在1)和2)分支中,時間為 o(1);而在 3)中,除掉移動 j 的步驟,時間也為 o(1),而移動 j 的步驟已經被計算到迴圈所需要的次數中了。因此,整個演算法的複雜度為 o(n )。

另外一種思路:

使用乙個256元素大小的hash表儲存字元出現的個數.

初始時將hash中在s2中出現了的元素初始化為0,其餘初始化為-1.

兩指標p1,p2都指向s1首字元,在p2向後移動過程中,如果遇到了hash值為0時

則count計數加1,如果count的值等於s2的長度時證明p1~p2之間已經有了乙個

子串為s2了,

此時記錄下他們的位置和距離。繼續把p1向後移,注意更新hash值和count,

直到找到最短的

int findminstring(char *s1,char *s2)

//將字符集s2中的字元對應的陣列元素初始化為0,s2中的字元在hash中的值》=0

for(char *p=s2;*p!='\0';p++)

char *p1=s1;

char *p2=s1;

char *min_p1=s1;

char *min_p2=s1;

int minlen=int_max;

int count=0;//標識已經是否有了乙個串中包含字符集s2

int len_s2=strlen(s2);

while(*p2!='\0' || len_s2==count)

p2++;

}if(count==len_s2)//即找到了乙個串中包含s2中所有字元的串

{if(p2-p1

求包含所有顏色的最小子串

求包含所有顏色的最小子串 演算法 對顏色編號,以顏色為鍵用陣列儲存子串中該色最後出現位置,未出現顏色賦值為 1,colorsize記錄子串中已出現顏色數,當顏色都包含時比較串長,並儲存此時子串資訊 public class allcolor if colorsize m int firstcolor...

判斷給定中文字元所屬字符集的方法

常見的中文字符集有 gb2312字符集 gbk 字符集 big5字符集 gb 18030字符集。其中gb2312字符集 gbk 字符集 big5字符集都是採用兩個位元組表示乙個漢字。下面的程式中h表示字元的高位元組位 l表示字元的低位元組位,十六進製制數值表示的是各種字元編碼集的邊界。public ...

找符合條件的最小子串

例項 s adobecodebanc t abc s最小符合條件t的最小子串為 banc algorithm 這裡首先要把t串字元放在雜湊表中,碰到t中字元則減1,這裡要注意hash中的個數可以為負數,因為比如abbbbbbca 找到abc很明顯是最後乙個子串,遍歷到後面的b時,它可能有用。那麼怎麼...