關於題目理解,請注意和最長公共子序列的區別,最長公共子字符串的解法是動態規劃,但是比較難想到表的構造方法。
注意到,設給定字符串為str1 和 str2 ,二者的長度分別是 len1 和 len2 ,那麼解空間大小之多是len1*len2?(假設最長公共子字符串為substr_common,那麼substr_common在str1 中的結束位置或者起始位置只有len1種選擇,而在str2中則最多len2種選擇,故解空間最大為len1*len2)。對於解空間中的每一個解都對應著自己的長度,因為一旦結束位置都確定,即意味著公共子字符串找到了。
如果用蠻力法做的話,遍歷解空間需要時間復雜度為O(len1*len2),然後確定每個解的長度需要的時間是O(min(len1,len2)),所以基本上是個O(n3)的算法,顯然不妥。
前面說過,這道題可以用動態規劃來做,動態規劃最關鍵的是找到最優子結構,一般來說,最優子結構意味著問題可以找到一種遞歸的,不斷縮小問題規模的解決方式,與分治法不同,動態規劃的小問題之間有重疊,但是這個問題好像不是那麼好找到一種遞歸的表達形式。
要縮小問題規模,一種最簡單的想法肯定是縮小str1規模,或者縮小str2規模,或者二者同時縮小,而縮小的方式肯定是增減元素了,能用動態規劃做必定有個自底向上的過程,很少有直接二分的, 直接二分是分治法的策略,所以增減一般在頭尾增減,以此題為例,為保持一致性,在分析題目的過程設兩個字符串都由尾部向頭減少,而在解決問題的過程中是由頭部向尾部增加。我們可以構建一張表count[len1][len2] , 其中count[i][j] 表示如果公共子字符串在str1的第i個位置結束,在str2的第j個位置結束時公共子字符串的長度。從底向上開始構建這張表,下面是增減的描述:假設目前需要計算count[i][j],有以下幾種情況:
1. str1[i] == str2[j] : 此時count[i][j] = count[i-1][j-1]+1
2. str1[i] != str2[j] : 很顯然,以i,j為結束點的解 count[i][j] = 0 .
因此,一旦知曉了count[i-1][0,….,len2] ,count[i][0,….,len2]可以順序獲得,以str1 = “1234”, str2 = “2345”為例,表的構造方式如下:
只要在構建表的過程中記住當前最長的子字符串長度和結束位置,就可以很輕易地打印出最長公共子字符串,與之相對應的代碼如下: