求最长回文串-从动态规划到马拉车之路(上)
要解決的問題:
給定一個字符串,要求求出這個字符串中的最長的回文串子串。
例子:
cbddba的最長回文子串為 bddb
cbdedba的最長回文子串為dbedb
由上面的例子可以看到,在考慮回文子串的問題時需要考慮奇偶性。因?yàn)槠婊匚年P(guān)于中心的某個字符對稱,而偶回文關(guān)于最中心的兩個元素之間的間隙對稱。
一、動態(tài)規(guī)劃法
在動態(tài)規(guī)劃的思想中,總是希望把問題劃分成相關(guān)聯(lián)的子問題;然后從最基本的子問題出發(fā)來推導(dǎo)較大的子問題,直到所有的子問題都解決。
首先要看一個較大的子問題與一個較小的子問題之間的關(guān)系:
首先建立如下的函數(shù):
那么就能有如下的遞推關(guān)系:
當(dāng)p(i+1,j-1)? = true 的時候,如果有si == sj,那么 p(i,j)=true;也就是 abba中的bb為回文串,那么在bb左邊是a,在bb右邊也是a,相同;所以有abba也是回文串。
形式化表示如下: p(i,j) = p(i+1,j-1) and si==sj? ? 這就建立了較大問題與較小問題之間的關(guān)系。
然后考慮基本情況,一個最基本的回文串有兩種情況(奇偶性):
(1) 最基本的奇回文,只有一個字符,而且恒成立;形式化表示為 p(i,i) = true
(2)最基本的偶回文,有兩個字符,當(dāng)且僅當(dāng)兩個字符相等的時候成立p(i,j) = (si==sj and j=i+1)
最后獲得如下判斷一個字符是否為回文串的分段函數(shù):
有了公式,就是用代碼實(shí)現(xiàn)了;下面給出了我的python實(shí)現(xiàn)。
# -*- coding:utf-8 -*- # Author: Evan Mi# 測試的字符串 str_exp = "babad" # 用來保存動態(tài)規(guī)劃過程的表 1表示true 0表示false longest_palindromes = [[-1] * len(str_exp) for i in range(len(str_exp))] # longest_len 用來保存最長的回文串的長度 longest_len = 1 # 從長度為1的回文子串開始填表 for p_len in range(1, len(str_exp)+1):for i in range(len(str_exp)):j = p_len + i - 1if j < len(str_exp):if i == j:longest_palindromes[i][j] = 1elif j == i + 1 and str_exp[i] == str_exp[j]:longest_palindromes[i][j] = 1longest_len = p_lenelif j > i+1 and longest_palindromes[i+1][j-1] == 1 and str_exp[i] == str_exp[j]:longest_palindromes[i][j] = 1longest_len = p_lenelse:longest_palindromes[i][j] = 0 # 搜索結(jié)果表,打印出所有的最優(yōu)解 for i in range(len(str_exp)):for j in range(len(str_exp)):if longest_palindromes[i][j] == 1 and j-i+1 == longest_len:print(str_exp[i:j+1])在動態(tài)規(guī)劃中,最最最核心的就是填表了,就以程序中的例子"babad"舉例,說明一下填表的過程;
首先我們要填的表是如下的一張表二維表(因?yàn)閜函數(shù)中有i,j兩個變量),其中綠色的部分是真實(shí)的表格,其他的是我家的解釋表頭。
填表過程如下:首先填長度為1的;
?
???然后填長度為2的;
?
???接著是長度為3的:
??長度為4的:
??最后是長度為5的:
填表完成之后,求最優(yōu)解就是查詢了;對于時間復(fù)雜度,動態(tài)規(guī)劃的時間復(fù)雜度在構(gòu)建表的過程中的基本操作,所以時間復(fù)雜度是O(n^2);空間復(fù)雜度,就是上面的二維數(shù)組,也是O(n^2)。而且從在空間浪費(fèi)(主對角線下面的空間沒有使用,浪費(fèi)了一般的空間)。有很多針對空間上的優(yōu)化方法,下面給出一種空間復(fù)雜度為O(1)的算法;
二、空間復(fù)雜度為O(1)的算法
該算法的主要思想就遍歷所有的字符(下標(biāo)為i)是以第i個數(shù)(對應(yīng)奇回文)或者第i個數(shù)和第i+1個數(shù)之間的間隙(對應(yīng)偶回文)為中心向兩邊擴(kuò)展,直到擴(kuò)展以后不再是回文,那么就停止擴(kuò)展,如果回文長度比已知的最長回文長,那么記錄下該回問的開始位置和結(jié)束位置為最長回文;
還是用"babad"來作為例子,過程如下:
代碼實(shí)現(xiàn)如下:
# -*- coding:utf-8 -*- # Author: Evan Mi import mathdef expandAroundCenter(left, right, s):"""從left和right之間開始擴(kuò)展,如果left==right就是以left/right為中心進(jìn)行擴(kuò)展"""rLeft = leftrRight = rightwhile rLeft >= 0 and rRight < len(s) and s[rLeft] == s[rRight]: # 進(jìn)行擴(kuò)展rLeft -= 1rRight += 1"""針對于返回的長度,因?yàn)樵趙hile循環(huán)停止的時候,rLeft和rRight都已經(jīng)在要求的回文串之外了所以回文串的長度為rRight - rLeft - 1,自己可以畫個過程圖,一目了然。"""return rRight - rLeft - 1s = "babad" start = 0 end = 0for i in range(len(s)):odd_len = expandAroundCenter(i, i, s) # i為中心的擴(kuò)展even_len = expandAroundCenter(i, i + 1, s) # i 和 i+1之間的空隙為中心進(jìn)行擴(kuò)展lens = max(odd_len, even_len) # 取得本次擴(kuò)展的最大值if lens > (end-start+1): # 如果本次的長度比記錄的回文的長度也就是end-start+1大,進(jìn)行替換# 需要注意的是,已經(jīng)知道了位置i,不管是以i為中心擴(kuò)展了長為lens的回文還是# 以i和i+1的空隙為中心擴(kuò)展了長為lens的回文。下面的start和end的計(jì)算方法都成立start = i - math.floor((lens - 1) / 2) end = i + math.floor(lens/2)print(start, ":", end) print(s[start:end+1])
這兩個算法不論在空間、結(jié)果上有什么不同,它們的時間復(fù)雜度都是相同的;接下來就分析一下“馬拉車”算法,該算法把時間復(fù)雜度降到了線性范圍內(nèi)。額,請見下篇博客。
PS:很多人都分析了"馬拉車"算法,但是也阻擋不了我征服它的步伐。相信自己對它一定有獨(dú)到的見解。
總結(jié)
以上是生活随笔為你收集整理的求最长回文串-从动态规划到马拉车之路(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 理论与实践:您的小数点到哪里去
- 下一篇: ECS框架学习