給定乙個字元集合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時,它可能有用。那麼怎麼...