題目:最長迴文子串java
描述:給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲 1000。算法
示例:編程
迴文字符串可能咱們不少人在學習編程語言基礎時都練習過,可是隻是判斷了一個字符串是不是迴文的而已,而今天的題目要複雜得多了。若是咱們要找出全部的子串,再一一判斷它是否是迴文的,可能須要O(n<sup>3</sup>)的時間複雜度,因此咱們要對它進行優化。下面咱們要介紹的兩種方法,能夠將此問題的時間複雜度分別下降到O(n<sup>2</sup>)和O(n)。數組
迴文字符串的特性就是中心對稱,那麼咱們能夠假定每一個字符都是一個迴文子串的中心字符,而後以此字符爲中心向兩側擴展,直到再也不是迴文子串爲止。這樣咱們就找到了以每一個字符爲中心的最長迴文子串,也就等於找到了最長的迴文子串。不過這裏有一點須要注意,那就是一個迴文串的中心多是一個字符,例如"aba"就是以字符 'b' 爲中心,也多是在兩個字符之間,例如"abba"的中心就在兩個 'b' 字符之間,只要處理好這兩種狀況,代碼就很簡單了,參考以下:app
public String longestPalindrome(String s) { int len = s.length(); if(len==0)return ""; int i = 0; int[] ends = new int[2]; while(i<len){ // 處理 "aba" 的狀況 getEnds(s, len, i-1, i+1, ends); // 處理 "abba" 的狀況 getEnds(s, len, i, i+1, ends); i++; } return s.substring(ends[0], ends[1]); } private void getEnds(String s, int len, int left, int right, int[] ends){ while(left >= 0 && right < len && s.charAt(left)==s.charAt(right)){ left--; right++; } if((ends[1]-ends[0])<(right-left-1)){ ends[0] = left+1; ends[1] = right; } }
這個方法也稱爲「馬拉車」算法,能夠說是解決迴文字符串最好的算法了,由於它的時間複雜度僅爲O(n)。下面咱們一步步分析這個算法是如何作到如此高效的。編程語言
首先,咱們用 '#' 或某個字符串中不存在的字符將字符串的每一個字符分隔開,並在首尾也插入一個 '#',以下所示:函數
以上操做解決了由於長度的奇偶性帶來的問題,新的字符串長度是 2*n+1,是始終爲奇數的。學習
接下來和中心擴展同樣,咱們也要肯定以一個字符爲中心的最長迴文子串的長度。Manacher算法中有一個概念叫做「迴文半徑」,它指的是從中心字符到最右側(或最左側)字符的距離,例如"#a#"的半徑就是2,。用此距離構造一個半徑迴文數組radius,咱們根據數組中的最大值就能夠肯定最長迴文子串的長度了。例如,以上示例中間的字符 'b' 的迴文半徑爲4,構成的迴文數組以下:優化
能夠發現,以原串第 i 個字符爲中心的最長迴文子串,就是radius數組第 i 位的值減 1 。那麼,咱們如何構造這個數組呢?ui
假設咱們已經求得了位置 i 處(相對於Manacher構造的字符串)的迴文半徑,也就是radius[i]的值,並記它的最右側位置爲 mx,以下所示:
而後咱們要計算位置 j 處的迴文半徑,其中 j>i。因爲radius[i]的值可能爲0,也可能較大,因此 j 可能小於 mx,也可能大於 mx,咱們先來看 j<=mx 時的狀況。以下所示,j<mx 時,j 關於 i 的對稱點 i-(j-i)(咱們記爲 k),也必定大於 mx關於 i 的對稱點 i-(mx-i)(咱們記爲mn),以下所示:
如今,讓咱們把目光聚焦到 j 的對稱點 k 上,它將爲咱們計算 j 的迴文半徑提供線索。由於radius[k]的值可能很小,以 k 爲中心的迴文子串徹底被以 i 爲中心的子串覆蓋,也可能較大超出了 i 的範圍,以下所示:
對於第一種狀況,能夠確定 j 也將被 i 徹底覆蓋,因此它的迴文半徑和 k 是相等的。第二種狀況則代表從 mx 以後的部分是未知的,須要進一步計算。
如今咱們再來考慮 j>=mx 的狀況,以下圖所示,這時 j 的迴文半徑一點都沒計算過,因此只能進一步進行計算。
根據以上思路,能夠參考的代碼以下:
public String longestPalindromeOptimize(String s) { if (s == null || s.length() == 0) { return ""; } char[] charArr = manacherStr(s); // 構造迴文半徑數組 int[] radius = new int[charArr.length]; // 當前已經計算過的最右側下標 int mx = -1; // i 表示 mx 最大時,對應的迴文子串的中心 int i = -1; // 最長迴文子串的長度 int max = 0; // 最長迴文子串的起點下標 int maxIndex = -1; for (int j = 0; j < radius.length; j++) { // 2*i-j 是 j 相對於 i 的對稱點,也就是文章中的 k。 // 當 j<mx 時,因爲以 k 爲中心的子串可能被 i 徹底覆蓋,也可能超出 i 的範圍 // 因此,計算 咱們只能計算出 radius[j] 在 mx-j這個範圍內的值 // 而當 j>mx 時,就須要徹底從頭開始計算了 radius[j] = j < mx ? Math.min(radius[2 * i - j], mx - j + 1) : 1; // 計算 j<mx 時超出 mx-j 範圍內的部分,或者 j 本就大於 mx時 while (j + radius[j] < charArr.length && j - radius[j] >= 0 && charArr[j - radius[j]] == charArr[j + radius[j]]) { radius[j]++; } // 更新 mx 的值和 i 的值 if (j+radius[j]>mx) { mx = j+radius[j]-1; i = j; } // 更新max的值 if (max<radius[j]) { max = radius[j]; // j 的起點座標,相對於Manacher字符數組而言是 j-radius[j]+1 // 而相對於原數組而言,這個值是由原數組的位置乘以2再加一獲得的,因此直接除以2便可 maxIndex = (j-radius[j]+1)/2; } } return s.substring(maxIndex,maxIndex+max-1); } private char[] manacherStr(String s){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { sb.append('#'); sb.append(s.charAt(i)); } sb.append('#'); return sb.toString().toCharArray(); }
Manacher算法若是咱們沒有聽過仍是很難想到的,不過不要氣餒,咱們只是來學習的,想不到能學到也算賺到,可以理解它也算一筆不小的收穫。下面這個題目較爲簡單,讓咱們放鬆一下吧。
題目:Z 字形變換
描述:將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。
好比輸入字符串爲 "LEETCODEISHIRING" 行數爲 3 時,排列以下:
L C I R E T O E S I I G E D H N
以後,你的輸出須要從左往右逐行讀取,產生出一個新的字符串,好比:"LCIRETOESIIGEDHN"。
請你實現這個將字符串進行指定行數變換的函數:
string convert(string s, int numRows);
示例 1:
示例 2:
L D R E O E I I E C I H N T S G
相關源碼請加QQ獲取。
【感謝您能看完,若是可以幫到您,麻煩點個贊~】
更多經驗技術歡迎前來共同窗習交流: 一點課堂-爲夢想而奮鬥的在線學習平臺 http://www.yidiankt.com/
![關注公衆號,回覆「1」免費領取-【java核心知識點】]
QQ討論羣:616683098
QQ:3184402434
想要深刻學習的同窗們能夠加我QQ一塊兒學習討論~還有全套資源分享,經驗探討,等你哦!