演算法 -- 四種方法獲取的最長“迴文串”,並對時間複雜進行分析對比&PHP
阿新 • • 發佈:2018-11-08
迴文串:
“迴文串”是一個正讀和反讀都一樣的字串,比如“level”或者“noon”等等就是迴文串。 -- 來自百度百科
關於獲取字串中最長的迴文串的演算法中,目前有很多演算法,本文中主要是用PHP來實現的演算法之一。
演算法一:暴力解法
暴力計算出所有的字串並判斷。時間複雜度:O(n^3)。
<?php //1. 判斷字串是否是迴文字串 function isPalindrome($str) { if ($str === strrev($str)) { return 1; } else { return 0; } } //2. 獲取一個字串有多少子串,設定了子串長度最小為2 function getAllSubstring($str) { $all_str_arr = []; for ($i = 0; $i < strlen($str); $i++) { for ($j = 0; $j < strlen($str) - $i + 1; $j++) { if (strlen(substr($str, $i, $j)) > 1) { $all_str_arr[] = substr($str, $i, $j); } } } return $all_str_arr; } //3. 獲取一維陣列中,元素長度最長的一組 function getArrMaxStr($arr) { $max = 0; foreach ($arr as $k => $v) { if (strlen($arr[$max]) < strlen($v)) { $max = $k; } } return $arr[$max]; } //4. 取得字串中,最長的迴文串 function getMaxPalindrome1($str) { if (isPalindrome($str)) { return $str; } else { // 拆分成多個字串,然後迴圈比較 foreach (getAllSubstring($str) as $k => $v) { if (isPalindrome($v)) { $max_str[] = $v; } } return getArrMaxStr($max_str); } }
以上四個步驟就是獲取字串中最長的迴文串,但是這個演算法只適合較短的字串,由上大家也可以看出,步驟2中求每一個子串時間複雜度O(N^2),步驟4中,再迴圈判斷子串是不是迴文串O(N),兩者是相乘關係,所以時間複雜度為O(N^3)。
暴力求解的優化:
每次判斷一個字串是否是迴文字串時,將每次的判斷結果存存起來,之後再用就不用重新計算了。但需要從後向前遍歷,這樣才會用得到提前儲存的結果。時間複雜度為O(n^2)。(這裡就不程式碼說明了)
演算法二:移動中心法
先假設某個位置為迴文字串的中心,然後查詢以此位中心的最長迴文字串。遍歷中心,即可找到全域性最長子串。時間複雜度為O(n^2)。
function getMaxPalindrome2($str) { if (is($str)) { return $str; } else { $len = strlen($str); // 假如得到的迴文串 為 偶數,abba,即中點是空隙 $max_str = ''; $max_arr = []; for ($i = 0; $i < $len; $i++) { $left = $i; $right = $i + 1; while ($right < $len && $left >= 0 && $str[$left] == $str[$right]) { if (strlen($max_str) <= $right - $left + 1) { $max_str = substr($str, $left, $right - $left + 1); if (is($max_str)) { $max_arr[] = $max_str; } } $left--; $right++; } } // 假如得到的迴文串 為 奇數,aba,即中點是字元 for ($i = 0; $i < $len; $i++) { $left = $i - 1; $right = $i + 1; while ($right < $len && $left >= 0 && $str[$left] == $str[$right]) { if (strlen($max_str) <= $right + 1 - $left) { $max_str = substr($str, $left, $right - $left + 1); if (is($max_str)) { $max_arr[] = $max_str; } } $left--; $right++; } } // 兩個合成一個的話,就是,只有當奇數迴文串的大於或等於偶數迴文串的時候, $longest_str = []; foreach ($max_arr as $v) { if (strlen($max_str) == strlen($v)) { $longest_str[] = $v; } } return $longest_str; } } // 判斷字串是否是迴文字串 function isPalindrome($str) { if ($str === strrev($str)) { return 1; } else { return 0; } }
演算法三:公共字串法
利用公共最長字串,時間複雜度:O(n^2)。
ps:方法getLongestSameStr(),在我的這篇文章裡:演算法 -- 求最長公共字串&PHP
function getMaxPalindrome3($str)
{
//1. 判斷是不是迴文
if (is($str)) return $str;
//2. 利用最長公共字串的方法求
$arr = getLongestSameStr($str, strrev($str));
return $arr;
}
// 判斷字串是否是迴文字串
function isPalindrome($str)
{
if ($str === strrev($str)) {
return 1;
} else {
return 0;
}
}
演算法四:Manacher演算法
經典的Manacher 演算法,優勢在於避免了演算法②奇偶數討論的問題,簡化了演算法②邊界判斷,還記錄了當前字串的“迴文狀態”,利用之前的迴文狀態來求當前迴文狀態 ,體現了演算法③動態規劃的思想,儲存資料,不用再次計算。時間複雜度為O(n)。
function getMaxPalindrome4($str)
{
// 初始化最大回文序列中間座標
$maxxy = 0;
// 初始化最大回文長度
$maxLength = 0;
// 初始化一個空陣列儲存每次的迴文序列中間座標(key)和迴文長度(value)
$arr = [];
// 通過在每個字元的兩邊都插入一個特殊的符號,將所有的迴文子串都轉換成奇數長度;
// 在字串的開始和結尾加入另一個特殊字元,這樣就不用特殊處理越界問題
$newStr = "^#" . implode("#", str_split($str)) . "#\0";
// 遞推,每次取一個數作為中間座標
for ($i = 2; $newStr[$i] != "\0"; $i++) {
// 每個中間座標的初始迴文長度為1
$arr[$i] = 1;
// 根據每個中間座標往兩頭匹配是否相等
while ($newStr[$i - $arr[$i]] == $newStr[$i + $arr[$i]]) {
// 每匹配成功一次,則當前座標的最大回文長度加一
$arr[$i]++;
}
// 判斷當前迴文長度是否大於最大的迴文長度,大於則進去if程式碼塊更新最大回文次數和更新最大回文中間座標
if ($arr[$i] > $maxLength) {
$maxLength = $arr[$i];//字串的長度
$maxxy = $i;//字串的末位置座標
}
}
// 擷取最大回文長度的字串
$res = substr($newStr, $maxxy - $maxLength + 1, $maxLength * 2 - 1);
// 清除開始加入的字元並返回
return str_replace('#', "", $res);
}
總結:由上可以清晰看出,時間複雜度④>③=②>①,演算法四是最優方案。
歡迎補充!.
QEの大獅子!