給定只包含字母的兩個字串a,b,求a,b兩個字串的最長公共子串行,要求構成子串行的子串長度都必須大於等於3。
比如」abcdefghijklmn」和」ababceghjklmn」,其最長滿足題意要求的子串行為」abcjklmn」,其由公共子串」abc」和」jklmn」組成。
這裡我們要注意子串和子串行的區別:
比如」abcdefghijklmn」和」ababceghjklmn」的最長公共子串就只是」jklmn」了。
首先我們來複習一道經典的題目:
給定只包含字母的兩個字串a,b,求a,b兩個字串的最長公共子串行。
比如"abcde"和"abdfg"的最長公共子串行為"abd"
對於最長公共子串行,我們知道解法為
dp[0][0..j] = 0 // 邊界
dp[0..i][0] = 0 // 邊界
for i = 1 .. n
for j = 1 .. m
if a[i] == b[j] then
dp[i][j] = dp[i - 1][j - 1] + 1
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
end if
end for
end for
而這一道題目是在最長公共子串行上加入了乙個條件:構成最長公共子串行的每乙個子串長度必須大於等於3.
乙個簡單的想法:我們求出最長公共子串行,然後將其中長度小於3的部分去掉。
顯然,這是不對的。
舉個例子:」aaabaa」和」acaaaca」的最長子串行為」aaaaa」。其對應關係為:
a aaba a
acaa aca
因為在」acaaaca」中第乙個字母a長度為1,所以我們需要去掉它,對應的我們也去掉了」aaabaa」中第乙個字母a。
. aaba a
. caa aca
此時構成」aaabaa」和」acaaaca」公共子串行的3個子串為」aa」,」a」和」a」,長度都小於了3,所以全部刪去,則得到了新的公共子串行長度為0。
這顯然不正確,因為實際有符合題意要求的公共子串行:
aaa baa
ac aaa ca
其中包含有長度為3的公共子串行。
對最大公共子串行的結果進行再次處理這個方法不可行,那麼我們只能從計算公共子串行的演算法著手。
首先我想我們可以做乙個預處理,用f[i][j]表示以a的第i個字母作為結尾的字首和以b的第j個字母作為結尾的字首的公共字尾的長度。這樣看上去似乎很繞,不如舉個例子:
a=」abcd」和b=」acbc」。f[3][4]的就表示a[1..3]和b[1..4]的公共字尾的長度,其中a[1..3]=」abc」,b[1..4]=」acbc」,其公共字尾為」bc」,所以f[3][4]=2.
預處理的偽**為:
for i = 1 .. n
for j = 1 .. m
if a[i] == b[j] then
f[i][j] = f[i - 1][j - 1] + 1
else
f[i][j] = 0
endif
endfor
endfor
有了這個預處理的陣列,我們可以在原來最大公共子串行上做這樣乙個改進:
dp[0][0..j] = 0 // 邊界
dp[0..i][0] = 0 // 邊界
for i = 1 .. n
for j = 1 .. m
if f[i][j] >= 3 then // 改進
dp[i][j] = dp[i - f[i][j]][j - f[i][j]] + f[i][j]
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
end if
end for
end for
這個改進的意義為:當我們出現乙個長度大於3的子串時,我們就直接將這個子串合併入我們的子串行。
加入這個改進後,我們通過了樣例的資料,這樣看上去似乎就應該沒什麼問題了。
然而事實並不是這樣,在這道題目中還隱藏著陷阱:
比如」abcdef」和」abcxcdef」
根據我們演算法,上面這個例子算出的結果為4,然而其實際的結果應該為6,即」abc」和」def」兩個公共子串構成的子串行。
那麼出錯的原因在哪?就在字串」cdef」上。
我們計算結果出4是因為將」cdef」看做了乙個整體,而將」abcdef」分割成了」ab」和」cdef」。
在dp的過程中f[6][7] = 4,我們使用了dp[6][7] = dp[2][3] + 4,而dp[2][3] = 0,所以dp[6][7] = 4。
ab cdef
abcxcdef
而實際上的最有解是將f[6][7]看作3,dp[6][7] = dp[3][4] + 3,其中dp[3][4] = 3,得到了dp[6][7] = 6。
abc def
abcxcdef
也就是說,如果我們將f[i][j]>3的子串進行分割,有可能得到更優的情況。因此我們需要進一步的改進:
dp[0][0..j] = 0 // 邊界
dp[0..i][0] = 0 // 邊界
for i = 1 .. n
for j = 1 .. m
dp[i][j] = 0
if f[i][j] >= 3 then // 改進
for k = 3 .. f[i][j] // 列舉分割長度
dp[i][j] = max(dp[i][j], dp[i - k][j - k] + k)
end for
end if
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
end for
end for
但是這樣的改進使得整個演算法的時間複雜度變為了o(n^3),當n=2100時,有可能會超時。
讓我們考慮一下如何進一步改進這個演算法。以上演算法複雜度高的地方在於對於每乙個(i, j),我們為了計算dp[i][j]都需要列舉分割長度k:
for k = 3 .. f[i][j] // 列舉分割長度
dp[i][j] = max(dp[i][j], dp[i - k][j - k] + k)
end for
這一步實際上我們計算了max, k=3..f[i][j]。我們不妨把它記作dp1[i][j],即:
dp1[i][j] = max = max
同時
dp1[i-1][j-1] = max
= max
我們可以發現,dp1[i][j]的展開式中除了dp[i-3][j-3]+3這一項,是與dp1[i-1][j-1]中的每一項一一對應的,並且剛好大1。所以實際上dp[i-1][j-1]計算時列舉過分割長度,我們並不需要再次計算:
dp1[i][j] = max
最後得到我們新的偽**如下,其中dp[i][j][0]對應上文分析中的dp[i][j], dp[i][j][1]對應dp1[i][j]:
dp[0][0..j][0..1] = 0 // 邊界
dp[0..i][0][0..1] = 0 // 邊界
for i = 1 .. n
for j = 1 .. m
dp[i][j][1] = 0
if f[i][j] >= 3 then // 改進
dp[i][j][1] = max(dp[i][j][1], dp[i - 3][j - 3][0] + 3) // 以長度3為分割
if (f[i][j] > 3) then
//按照dp[i-1][j-1][1]的分割方式分割,即直接將(i,j)接在(i-1,j-1)後面
dp[i][j][1] = max(dp[i][j][1], dp[i - 1][j - 1][1] + 1)
end if
end if
dp[i][j][0] = max(dp[i-1][j][0], dp[i][j-1][0], dp[i][j][1])
end for
end for
至此我這道題目也算是完整的解出了。
這個題目是在經典的動態規劃題目《最長公共子串行》上做了一點修改。雖然只增加了乙個條件,不過難度增大很多。能想出乙個複雜度是o(n^2)的正確演算法不是很容易,需要仔細分析清楚各種情況。一不小心就會掉進各種陷阱裡。
很多選手都能夠想到經典最長子序列的改進演算法而獲得80分。
剩下的測試點則對應了演算法分析中提到的陷阱,所以能否找出這種特殊的例子也是解決這道題的關鍵。
不過微軟的出題人似乎沒有想太為難大家,資料並不是很強。在實際的比賽中,o(n^3)的演算法也能拿到滿分,最終該題目的通過率為9%。
很多o(n^2)的程式不能通過」babad」和」babacabad」這組資料。
#include
#include
#include
using
namespace
std;
char a[2110], b[2110];
int dp[2110][2110][2], f[2110][2110];
int main()
}printf("%d\n", dp[len_a][len_b][1]);
return
0;}
hiho一下 第六十二周
在瀏覽網頁的時候,快取技術能夠迅速地顯示頁面。這裡我們對瀏覽器的快取技術進行簡化 我們認為瀏覽器的快取大小為m,表示快取可以儲存m個頁面。當使用者訪問url時,瀏覽器會先到快取中查詢是否有該頁面的記錄,如果有則直接從快取中提取資料 否則,會傳送網路請求,從internet獲取該頁面,並將該頁面放入快...
第六十周學習生活總結
今天是2020年4月12日。天氣真的是超級熱,中午那一會兒覺得都能穿短袖了,可誰也想不到兩天前還下了雪 沒忽悠你們,是真的下雪了,雖然沒有積雪,但那天的雨夾雪是絕對能讓你裹緊自己的小棉襖的 做了這乙個月的後台,我發現後台的套路很簡單,而且頁面寫起來真的很方便啊,基本就是乙個模板一直加加加。不過後台也...
hiho一下 第十周 分治
時間限制 10000ms 單點時限 1000ms 記憶體限制 256mb 描述在參與過了美食節之後,小hi和小ho在別的地方又玩耍了一陣子,在這個過程中,小ho得到了乙個非常有意思的玩具 一棵由小球和木棍連線起來的二叉樹!小ho對這棵二叉樹愛不釋手,於是給它的每乙個節點都標記了乙個標號 乙個屬於a....