日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

求最长回文串-从动态规划到马拉车之路(下)

發(fā)布時(shí)間:2025/3/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 求最长回文串-从动态规划到马拉车之路(下) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

預(yù)備知識(shí):

(1)在一個(gè)數(shù)軸上有兩點(diǎn)i和j(i<=j)關(guān)于點(diǎn)m對(duì)稱,那么有 i = 2m-j;

?證明: 因?yàn)?i<=j 且 i 和 j 關(guān)于 m 對(duì)稱,那么有 (i + j)/ 2 = m

??所以 i = 2m - j;

(2)回文串的對(duì)稱性:

?由回文串的定義可以知道,奇回文關(guān)于最中心的數(shù)對(duì)稱,而偶回文關(guān)于最中心的兩個(gè)數(shù)之間的間隔對(duì)稱。

根據(jù)回文串的對(duì)稱性質(zhì),可以得到如下圖所示的有用結(jié)論:


?如圖所示,A是一個(gè)從0到N的回文串,且M是回文串A的中心,那么就能推出字符串

AL[0...f(M)] ==AR[g(M)...N],其中f(M)為對(duì)M向下取整,g(M)為對(duì)M向上取整;

?那么如果圖中的B是回文串(B包含在A的左半?yún)^(qū)內(nèi)),且以I為中心;而且J和I關(guān)于M對(duì)稱,

那么以J為中心一定存在一個(gè)回文串C,且C和B完全相同;

?推論:


?如上圖所示,如果J關(guān)于M的對(duì)稱點(diǎn)I為中心的回文串的最左端B0超出了回文串A的最左端A0,

那么我們就無(wú)法肯定地說(shuō)一定存在一個(gè)以J為中心的回文串C,且C與B完全相同了(因?yàn)閇B0,A0)

范圍內(nèi)的字符在回文A以外,不受對(duì)稱性的控制)。但是如果我們舍棄B的[B0,A0)部分,也就是取

上圖中的[A0,A0']為以I為中心的回文串B';那么就能確定以J為中心的[AN',AN]也是回文串,

[AN',AN]和[A0,A0']完全相同(其中的AN'和A0'也關(guān)于M對(duì)稱)。

(3) 把奇回文和偶回文的分類考察統(tǒng)一為一種考察

?這是一種技巧性的處理方式,給定了一個(gè)字符串babad;

在考察babad為是不是回文的時(shí)候,我們要考慮它是否關(guān)于最中心的b對(duì)稱(即是否為ba|b|ad),

在考察子串baba是否為回文串的時(shí)候,要考慮是否關(guān)于最中心的ab之間的間隙對(duì)稱(即是否為ba|ba);

這就使得我們需要分情況去討論字符串是否為回文串。

?現(xiàn)在對(duì)字符串做如下的處理,華麗變身為如下的字符串#b#a#b#a#d#,對(duì)與這個(gè)字符串,

你要確定它本身是不是回文串,依然需要分成奇回文和偶回文來(lái)討論。但是如果你通過(guò)它來(lái)確定它的

生成源(babad)是否為回文串,那么就只需要按奇回文的情況來(lái)考慮就可以了。額,具體來(lái)說(shuō)就是,

如果我考慮#b#a#b#a#d#是否以某個(gè)#為中心是回文串的時(shí)候,實(shí)際就是考察生成源是否以兩個(gè)字母之間

的間隙為中心為回文串(偶回文情況);如果我考慮#b#a#b#a#d#是否以某個(gè)非#的字符為中心的回文串時(shí),

實(shí)際就是考察生成源是否是以該字母為中心的回文串(奇回文情況);

?用上篇中提到的擴(kuò)展法來(lái)處理#b#a#b#a#d#,且只考慮奇回文情況,那么可以得到下表:


?從表中我們看出,在只考慮奇回文的情況下,求的的每個(gè)回文串的長(zhǎng)度設(shè)為L(zhǎng);那么有生成源的回文的長(zhǎng)度為(L-1)/2;并且長(zhǎng)度為L(zhǎng)的回文子串的開始字符和結(jié)束字符在該字符串中的下標(biāo)分別設(shè)為S和E;

那么S/2向下取整為對(duì)應(yīng)的生成源的回文子串在生成源中的開始下標(biāo);(E-1)/2向下

取整為對(duì)應(yīng)的生成源的回文子串在生成源中的結(jié)束下標(biāo)。

例如(#b#a#b#a#d#)中第6行有#a#b#a#的回文子串長(zhǎng)度為7,開始下標(biāo)為2,結(jié)束下標(biāo)為8

?那么就對(duì)應(yīng)于生成源(babad)的回文子串a(chǎn)ba長(zhǎng)度為3,開始下標(biāo)為1,結(jié)束下標(biāo)為3。


“馬拉車算法”

?首先定義長(zhǎng)度為奇數(shù)的字符串S的半徑為r(S) = (S.length()-1)/2 ;

如下圖所示:


?然后定義輸入 radses ,radses[i]用來(lái)存放已經(jīng)求的的以i為中心的奇回文的半徑r;

?再定義, current_center,current_rights_index分別為已經(jīng)求得半徑的奇回文中結(jié)束下標(biāo)最大

的奇回文的中心,最右下標(biāo)。為什么要記錄這個(gè)量,其實(shí)是為了盡可能的取減少重

復(fù)計(jì)算(也就是盡可能的利用預(yù)備知識(shí)中的(2));因?yàn)閏urrent_rights_index越大,

自加后i在它的左側(cè)的可能就越大;這樣就更可能的對(duì)這個(gè)i利用預(yù)備知識(shí)(2),

直接確定一個(gè)以i為中心的較大的初始半徑,減少比較次數(shù)。

?定義,result_center,result_rads所求的最長(zhǎng)的回文串的中心和半徑;

代碼實(shí)現(xiàn)如下:

# -*- coding:utf-8 -*- # Author: Evan Mi import math # 要測(cè)試的字符串 test_str = 'babad' # enriched_len 是指#b#a#b#a#d#的長(zhǎng)度 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): # 遍歷所有增強(qiáng)后的字符串#b#a#b#a#d# 這里我并沒(méi)有真的構(gòu)建該字符串,只是假裝有一個(gè)if current_rights_index > i:"""如過(guò)i在current_rights_index的左側(cè),那么就可以利用預(yù)備知識(shí)(2)來(lái)確定更長(zhǎng)的初始半徑symmetric_i = 2*current_center - i 預(yù)備知識(shí)(1) 求出i關(guān)于current_center對(duì)稱的點(diǎn)的下標(biāo)radses[i] = min(radses[symmetric_i], current_rights_index - i)當(dāng)i在current_rights_index的左側(cè)時(shí),預(yù)備知識(shí)(2)或其推論,必然有一個(gè)成立;current_rights_index - i(推論中的AN - J 由對(duì)稱性 也是 I - A0)是推論成立時(shí)的半徑,radses[symmetric_i]是預(yù)備知識(shí)(2)成立時(shí)的情況。這里用min,讀者可以從預(yù)備知識(shí)(2)中的圖上看出來(lái),推論成立的時(shí)候一定是current_rights_index - i小,而反之則是radses[symmetric_i];當(dāng)然也可能一起成立,相等取最小也是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 半徑向左擴(kuò)展1,不超出列表范圍i + (radses[i] + 1) < enriched_len 半徑向右擴(kuò)展1,不超出列表范圍((i - (radses[i] + 1)) % 2 == 0 and (i + (radses[i] + 1)) % 2 == 0) 擴(kuò)展后的最左端和最右端的字符都是#這里要說(shuō)明,在給一個(gè)字符串填充#以后,所有的下標(biāo)為偶數(shù)的字符都是#,大家可以自己證明。(test_str[math.floor((i - (radses[i] + 1))/2)] == test_str[math.floor((i + (radses[i] + 1))/2)]))在被#號(hào)填充后的字符串中,所有不是#號(hào)的字符,也就是來(lái)自源字符串的字符的下標(biāo)除以2,向下取整,就是對(duì)應(yīng)與該字符在源字符串中的下標(biāo)。第三和第四個(gè)條件的或組合,其實(shí)就是判斷半徑擴(kuò)展一以后兩端的字符是否相同。這里因?yàn)椴](méi)有真正的去用#來(lái)填充原列表,而是在想象中完成,所以我們通過(guò)第三個(gè)判斷來(lái)判斷在想象中填充過(guò)的字符串的兩個(gè)對(duì)應(yīng)位置是否都是#號(hào),第四個(gè)判斷條件則是判斷想象中填充過(guò)的字符串的兩個(gè)來(lái)自于源字符串的字符是否相同; 當(dāng)然,你也可以真的建立一個(gè)填充過(guò)的列表,那么第三和第四條判斷就變成了:填充過(guò)的數(shù)組[i-radses[i]] == 填充過(guò)的數(shù)組[i+radses[i]];但是多用了n+1個(gè)空間"""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 # 兩端都可以擴(kuò)展,那么半徑擴(kuò)展1if current_rights_index < i + radses[i]: # 保持current_rights_index為最右的端點(diǎn)current_rights_index = i + radses[i]current_center = iif result_rads < radses[i]: # 保存result_rads為半徑最大(也就是我們要求的最長(zhǎng)回文串)result_rads = radses[i]result_center = i""" 這里用到了前提(3),從填充的字符的開始和結(jié)束下標(biāo)轉(zhuǎn)換為源字符的開始和結(jié)束下標(biāo)解釋結(jié)束下標(biāo)應(yīng)該為 (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)])


時(shí)間復(fù)雜度(原子操作是比較),下圖中的每一個(gè)綠色,都代表該方格與它左邊的對(duì)稱方格進(jìn)行了一次比較:


可以看到,比較的次數(shù)不可能是 n^2+(......),而是 an+常數(shù),所以時(shí)間復(fù)雜度是O(n)




總結(jié)

以上是生活随笔為你收集整理的求最长回文串-从动态规划到马拉车之路(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。