C++實(shí)現(xiàn)LeetCode(5.最長回文子串)
[LeetCode] 5. Longest Palindromic Substring 最長回文子串
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd"
Output: "bb"
這道題讓我們求最長回文子串,首先說下什么是回文串,就是正讀反讀都一樣的字符串,比如 "bob", "level", "noon" 等等。那么最長回文子串就是在一個(gè)字符串中的那個(gè)最長的回文子串。LeetCode 中關(guān)于回文串的題共有五道,除了這道,其他的四道為 Palindrome Number,Validate Palindrome,Palindrome Partitioning,Palindrome Partitioning II,我們知道傳統(tǒng)的驗(yàn)證回文串的方法就是兩個(gè)兩個(gè)的對稱驗(yàn)證是否相等,那么對于找回文字串的問題,就要以每一個(gè)字符為中心,像兩邊擴(kuò)散來尋找回文串,這個(gè)算法的時(shí)間復(fù)雜度是 O(n*n),可以通過 OJ,就是要注意奇偶情況,由于回文串的長度可奇可偶,比如 "bob" 是奇數(shù)形式的回文,"noon" 就是偶數(shù)形式的回文,兩種形式的回文都要搜索,對于奇數(shù)形式的,我們就從遍歷到的位置為中心,向兩邊進(jìn)行擴(kuò)散,對于偶數(shù)情況,我們就把當(dāng)前位置和下一個(gè)位置當(dāng)作偶數(shù)行回文的最中間兩個(gè)字符,然后向兩邊進(jìn)行搜索,參見代碼如下:
解法一:
class Solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size(), maxLen = 0, start = 0;
for (int i = 0; i < n - 1; ++i) {
searchPalindrome(s, i, i, start, maxLen);
searchPalindrome(s, i, i + 1, start, maxLen);
}
return s.substr(start, maxLen);
}
void searchPalindrome(string s, int left, int right, int& start, int& maxLen) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left; ++right;
}
if (maxLen < right - left - 1) {
start = left + 1;
maxLen = right - left - 1;
}
}
};
我們也可以不使用子函數(shù),直接在一個(gè)函數(shù)中搞定,我們還是要定義兩個(gè)變量 start 和 maxLen,分別表示最長回文子串的起點(diǎn)跟長度,在遍歷s中的字符的時(shí)候,我們首先判斷剩余的字符數(shù)是否小于等于 maxLen 的一半,是的話表明就算從當(dāng)前到末尾到子串是半個(gè)回文串,那么整個(gè)回文串長度最多也就是 maxLen,既然 maxLen 無法再變長了,計(jì)算這些就沒有意義,直接在當(dāng)前位置 break 掉就行了。否則就要繼續(xù)判斷,我們用兩個(gè)變量 left 和 right 分別指向當(dāng)前位置,然后我們先要做的是向右遍歷跳過重復(fù)項(xiàng),這個(gè)操作很必要,比如對于 noon,i在第一個(gè)o的位置,如果我們以o為最中心往兩邊擴(kuò)散,是無法得到長度為4的回文串的,只有先跳過重復(fù),此時(shí)left指向第一個(gè)o,right指向第二個(gè)o,然后再向兩邊擴(kuò)散。而對于 bob,i在第一個(gè)o的位置時(shí),無法向右跳過重復(fù),此時(shí) left 和 right 同時(shí)指向o,再向兩邊擴(kuò)散也是正確的,所以可以同時(shí)處理奇數(shù)和偶數(shù)的回文串,之后的操作就是更新 maxLen 和 start 了,跟上面的操作一樣,參見代碼如下:
解法二:
class Solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size(), maxLen = 0, start = 0;
for (int i = 0; i < n;) {
if (n - i <= maxLen / 2) break;
int left = i, right = i;
while (right < n - 1 && s[right + 1] == s[right]) ++right;
i = right + 1;
while (right < n - 1 && left > 0 && s[right + 1] == s[left - 1]) {
++right; --left;
}
if (maxLen < right - left + 1) {
maxLen = right - left + 1;
start = left;
}
}
return s.substr(start, maxLen);
}
};
此題還可以用動(dòng)態(tài)規(guī)劃 Dynamic Programming 來解,根 Palindrome Partitioning II 的解法很類似,我們維護(hù)一個(gè)二維數(shù)組 dp,其中 dp[i][j] 表示字符串區(qū)間 [i, j] 是否為回文串,當(dāng) i = j 時(shí),只有一個(gè)字符,肯定是回文串,如果 i = j + 1,說明是相鄰字符,此時(shí)需要判斷 s[i] 是否等于 s[j],如果i和j不相鄰,即 i - j >= 2 時(shí),除了判斷 s[i] 和 s[j] 相等之外,dp[i + 1][j - 1] 若為真,就是回文串,通過以上分析,可以寫出遞推式如下:
dp[i, j] = 1 if i == j
= s[i] == s[j] if j = i + 1
= s[i] == s[j] && dp[i + 1][j - 1] if j > i + 1
這里有個(gè)有趣的現(xiàn)象就是如果我把下面的代碼中的二維數(shù)組由 int 改為 vector<vector<int>> 后,就會(huì)超時(shí),這說明 int 型的二維數(shù)組訪問執(zhí)行速度完爆 std 的 vector 啊,所以以后盡可能的還是用最原始的數(shù)據(jù)類型吧。
解法三:
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int n = s.size(), dp[n][n] = {0}, left = 0, len = 1;
for (int i = 0; i < n; ++i) {
dp[i][i] = 1;
for (int j = 0; j < i; ++j) {
dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1]));
if (dp[j][i] && len < i - j + 1) {
len = i - j + 1;
left = j;
}
}
}
return s.substr(left, len);
}
};
最后要來的就是大名鼎鼎的馬拉車算法 Manacher's Algorithm,這個(gè)算法的神奇之處在于將時(shí)間復(fù)雜度提升到了 O(n) 這種逆天的地步,而算法本身也設(shè)計(jì)的很巧妙,很值得我們掌握,代碼實(shí)現(xiàn)如下:
解法四:
class Solution {
public:
string longestPalindrome(string s) {
string t ="$#";
for (int i = 0; i < s.size(); ++i) {
t += s[i];
t += '#';
}
int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0;
for (int i = 1; i < t.size(); ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resMx < p[i]) {
resMx = p[i];
resId = i;
}
}
return s.substr((resId - resMx) / 2, resMx - 1);
}
};
到此這篇關(guān)于C++實(shí)現(xiàn)LeetCode(5.最長回文子串)的文章就介紹到這了,更多相關(guān)C++實(shí)現(xiàn)最長回文子串內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++實(shí)現(xiàn)LeetCode(10.正則表達(dá)式匹配)
- C++實(shí)現(xiàn)LeetCode(7.翻轉(zhuǎn)整數(shù))
- C++實(shí)現(xiàn)LeetCode(6.字型轉(zhuǎn)換字符串)
- C++實(shí)現(xiàn)LeetCode(647.回文子字符串)
- C++實(shí)現(xiàn)LeetCode(132.拆分回文串之二)
- C++實(shí)現(xiàn)LeetCode(131.拆分回文串)
- C++實(shí)現(xiàn)LeetCode(125.驗(yàn)證回文字符串)
- C++實(shí)現(xiàn)LeetCode(44.外卡匹配)
相關(guān)文章
DSP中浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算--舉例及編程中的心得
本文主要講解DSP浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算舉例及編程中的心得 ,具有參考價(jià)值,需要的朋友可以參考一下。2016-06-06
C語言基礎(chǔ)隱式類型轉(zhuǎn)換與強(qiáng)制類型轉(zhuǎn)換示例解析
最接地氣的有關(guān)類型轉(zhuǎn)換的介紹,此處對于類型轉(zhuǎn)換的相關(guān)知識點(diǎn)做一些簡要的介紹,作者實(shí)屬初學(xué),難免文章中有內(nèi)容理解不到位或者有不當(dāng)之處,還請朋友們不吝指正,希望大家多多給予支持2021-11-11
Qt專欄之模態(tài)與非模態(tài)對話框的實(shí)現(xiàn)
這篇文章主要介紹了Qt專欄之模態(tài)與非模態(tài)對話框的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
C語言驅(qū)動(dòng)開發(fā)之內(nèi)核文件的讀寫
這篇文章主要為大家詳細(xì)介紹了C語言驅(qū)動(dòng)開發(fā)中內(nèi)核文件的讀寫的系列函數(shù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-06-06
c++利用windows函數(shù)實(shí)現(xiàn)計(jì)時(shí)示例
這篇文章主要介紹了c++利用windows函數(shù)實(shí)現(xiàn)計(jì)時(shí)示例,需要的朋友可以參考下2014-05-05
C++中四種強(qiáng)制轉(zhuǎn)換方式的區(qū)別
在C++中,有四種不同的強(qiáng)制轉(zhuǎn)換方式,它們分別是靜態(tài)轉(zhuǎn)換、動(dòng)態(tài)轉(zhuǎn)換、常量轉(zhuǎn)換和重新解釋轉(zhuǎn)換,下面通過示例代碼講解每種轉(zhuǎn)換的區(qū)別,感興趣的朋友跟隨小編一起看看吧2023-08-08
C++實(shí)現(xiàn)LeetCode(145.二叉樹的后序遍歷)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(145.二叉樹的后序遍歷),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
用C++實(shí)現(xiàn)一個(gè)鏈?zhǔn)綏5膶?shí)例代碼
本篇文章是對使用C++實(shí)現(xiàn)一個(gè)鏈?zhǔn)綏5拇a進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

