求最长回文串-从动态规划到马拉车之路(下)
預備知識:
(1)在一個數軸上有兩點i和j(i<=j)關于點m對稱,那么有 i = 2m-j;
?證明: 因為 i<=j 且 i 和 j 關于 m 對稱,那么有 (i + j)/ 2 = m
??所以 i = 2m - j;
(2)回文串的對稱性:
?由回文串的定義可以知道,奇回文關于最中心的數對稱,而偶回文關于最中心的兩個數之間的間隔對稱。
根據回文串的對稱性質,可以得到如下圖所示的有用結論:
?如圖所示,A是一個從0到N的回文串,且M是回文串A的中心,那么就能推出字符串
AL[0...f(M)] ==AR[g(M)...N],其中f(M)為對M向下取整,g(M)為對M向上取整;
?那么如果圖中的B是回文串(B包含在A的左半區內),且以I為中心;而且J和I關于M對稱,
那么以J為中心一定存在一個回文串C,且C和B完全相同;
?推論:
?如上圖所示,如果J關于M的對稱點I為中心的回文串的最左端B0超出了回文串A的最左端A0,
那么我們就無法肯定地說一定存在一個以J為中心的回文串C,且C與B完全相同了(因為[B0,A0)
范圍內的字符在回文A以外,不受對稱性的控制)。但是如果我們舍棄B的[B0,A0)部分,也就是取
上圖中的[A0,A0']為以I為中心的回文串B';那么就能確定以J為中心的[AN',AN]也是回文串,
且[AN',AN]和[A0,A0']完全相同(其中的AN'和A0'也關于M對稱)。
(3) 把奇回文和偶回文的分類考察統一為一種考察
?這是一種技巧性的處理方式,給定了一個字符串babad;
在考察babad為是不是回文的時候,我們要考慮它是否關于最中心的b對稱(即是否為ba|b|ad),
在考察子串baba是否為回文串的時候,要考慮是否關于最中心的ab之間的間隙對稱(即是否為ba|ba);
這就使得我們需要分情況去討論字符串是否為回文串。
?現在對字符串做如下的處理,華麗變身為如下的字符串#b#a#b#a#d#,對與這個字符串,
你要確定它本身是不是回文串,依然需要分成奇回文和偶回文來討論。但是如果你通過它來確定它的
生成源(babad)是否為回文串,那么就只需要按奇回文的情況來考慮就可以了。額,具體來說就是,
如果我考慮#b#a#b#a#d#是否以某個#為中心是回文串的時候,實際就是考察生成源是否以兩個字母之間
的間隙為中心為回文串(偶回文情況);如果我考慮#b#a#b#a#d#是否以某個非#的字符為中心的回文串時,
實際就是考察生成源是否是以該字母為中心的回文串(奇回文情況);
?用上篇中提到的擴展法來處理#b#a#b#a#d#,且只考慮奇回文情況,那么可以得到下表:
?從表中我們看出,在只考慮奇回文的情況下,求的的每個回文串的長度設為L;那么有生成源的回文的長度為(L-1)/2;并且長度為L的回文子串的開始字符和結束字符在該字符串中的下標分別設為S和E;
那么S/2向下取整為對應的生成源的回文子串在生成源中的開始下標;(E-1)/2向下
取整為對應的生成源的回文子串在生成源中的結束下標。
例如(#b#a#b#a#d#)中第6行有#a#b#a#的回文子串長度為7,開始下標為2,結束下標為8
?那么就對應于生成源(babad)的回文子串aba長度為3,開始下標為1,結束下標為3。
“馬拉車算法”
?首先定義長度為奇數的字符串S的半徑為r(S) = (S.length()-1)/2 ;
如下圖所示:
?然后定義輸入 radses ,radses[i]用來存放已經求的的以i為中心的奇回文的半徑r;
?再定義, current_center,current_rights_index分別為已經求得半徑的奇回文中結束下標最大
的奇回文的中心,最右下標。為什么要記錄這個量,其實是為了盡可能的取減少重
復計算(也就是盡可能的利用預備知識中的(2));因為current_rights_index越大,
自加后i在它的左側的可能就越大;這樣就更可能的對這個i利用預備知識(2),
直接確定一個以i為中心的較大的初始半徑,減少比較次數。
?定義,result_center,result_rads所求的最長的回文串的中心和半徑;
代碼實現如下:
# -*- coding:utf-8 -*- # Author: Evan Mi import math # 要測試的字符串 test_str = 'babad' # enriched_len 是指#b#a#b#a#d#的長度 enriched_len = len(test_str)*2 + 1# radses = [0]*enriched_lencurrent_rights_index = 0 current_center = 0result_rads = 0 result_center = 0for i in range(enriched_len): # 遍歷所有增強后的字符串#b#a#b#a#d# 這里我并沒有真的構建該字符串,只是假裝有一個if current_rights_index > i:"""如過i在current_rights_index的左側,那么就可以利用預備知識(2)來確定更長的初始半徑symmetric_i = 2*current_center - i 預備知識(1) 求出i關于current_center對稱的點的下標radses[i] = min(radses[symmetric_i], current_rights_index - i)當i在current_rights_index的左側時,預備知識(2)或其推論,必然有一個成立;current_rights_index - i(推論中的AN - J 由對稱性 也是 I - A0)是推論成立時的半徑,radses[symmetric_i]是預備知識(2)成立時的情況。這里用min,讀者可以從預備知識(2)中的圖上看出來,推論成立的時候一定是current_rights_index - i小,而反之則是radses[symmetric_i];當然也可能一起成立,相等取最小也是OK的。"""symmetric_i = 2*current_center - iradses[i] = min(radses[symmetric_i], current_rights_index - i)else:radses[i] = 0"""i - (radses[i] + 1) >= 0 半徑向左擴展1,不超出列表范圍i + (radses[i] + 1) < enriched_len 半徑向右擴展1,不超出列表范圍((i - (radses[i] + 1)) % 2 == 0 and (i + (radses[i] + 1)) % 2 == 0) 擴展后的最左端和最右端的字符都是#這里要說明,在給一個字符串填充#以后,所有的下標為偶數的字符都是#,大家可以自己證明。(test_str[math.floor((i - (radses[i] + 1))/2)] == test_str[math.floor((i + (radses[i] + 1))/2)]))在被#號填充后的字符串中,所有不是#號的字符,也就是來自源字符串的字符的下標除以2,向下取整,就是對應與該字符在源字符串中的下標。第三和第四個條件的或組合,其實就是判斷半徑擴展一以后兩端的字符是否相同。這里因為并沒有真正的去用#來填充原列表,而是在想象中完成,所以我們通過第三個判斷來判斷在想象中填充過的字符串的兩個對應位置是否都是#號,第四個判斷條件則是判斷想象中填充過的字符串的兩個來自于源字符串的字符是否相同; 當然,你也可以真的建立一個填充過的列表,那么第三和第四條判斷就變成了:填充過的數組[i-radses[i]] == 填充過的數組[i+radses[i]];但是多用了n+1個空間"""while i - (radses[i] + 1) >= 0 and i + (radses[i] + 1) < enriched_len and \(((i - (radses[i] + 1)) % 2 == 0 and (i + (radses[i] + 1)) % 2 == 0) or(test_str[math.floor((i - (radses[i] + 1))/2)] ==test_str[math.floor((i + (radses[i] + 1))/2)])):radses[i] = radses[i] + 1 # 兩端都可以擴展,那么半徑擴展1if current_rights_index < i + radses[i]: # 保持current_rights_index為最右的端點current_rights_index = i + radses[i]current_center = iif result_rads < radses[i]: # 保存result_rads為半徑最大(也就是我們要求的最長回文串)result_rads = radses[i]result_center = i""" 這里用到了前提(3),從填充的字符的開始和結束下標轉換為源字符的開始和結束下標解釋結束下標應該為 (result_center+result_rads-1)/2向下取整, 由于在python中,為前開后閉,所以變成了 (result_center+result_rads-1+1)/2向下取整, 也就是(result_center+result_rads)/2向下取整, """ print(test_str[math.floor((result_center-result_rads) / 2):math.floor((result_center+result_rads)/2)])
時間復雜度(原子操作是比較),下圖中的每一個綠色,都代表該方格與它左邊的對稱方格進行了一次比較:
可以看到,比較的次數不可能是 n^2+(......),而是 an+常數,所以時間復雜度是O(n)
總結
以上是生活随笔為你收集整理的求最长回文串-从动态规划到马拉车之路(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于 VB,VC,Delphi,SDK
- 下一篇: .net cf的label问题