判斷連結串列是否為迴文串以及關於迴文串問題的討論
阿新 • • 發佈:2019-01-02
最近在看程式設計師面試金典,在連結串列部分看到有一題問如何判斷連結串列是否是迴文串,然後想到白書中也有對最長迴文子串的討論,故想做一點總結。
一、判斷連結串列是否為迴文串
連結串列的資料結構是這樣子滴:
public class Node { public int val; public Node next; public Node(int val) { this.val = val; next = null; } public void appendToTail(Node node) { Node curNode = this; while (curNode.next != null) { curNode = curNode.next; } curNode.next = node; } }
這是面試金典中的一個題目,大體的思路有以下兩種
1、將連結串列前半段入棧,然後從後半段開始依次與棧頂元素比較。
Java程式碼如下:
/** * 將連結串列前半部分放入棧中(注意奇數和偶數個) * 當遍歷連結串列後半部分的時候,一次彈出棧中的 * 元素,檢驗對應位置的元素是否相同。 * @param head * @return */ private static boolean isPalindrome(Node head) { //如果連結串列為空或者只有一個節點,那麼連結串列是迴文串 if (head == null || head.next == null) { return true; } //設定快慢指標,控制入棧的節點個數為節點數的一半。 Node fastNode = head, slowNode = head; Stack<Node> stack = new Stack<>(); while (fastNode != null && fastNode.next != null) { stack.push(slowNode); fastNode = fastNode.next.next;//快指標走兩步 slowNode = slowNode.next;//慢指標走一步 } //如果最後快指標是空,則連結串列節點數為偶數,且此時慢指標 //指向中間位置(偶數有兩個中心位置)的右側,故不需要後移。 //若不為空,則連結串列節點數為奇數,且慢指標指向中心位置, //若要比較需要向後移動一位。 if (fastNode != null) { slowNode = slowNode.next; } //遍歷連結串列後半部分並以此與棧中元素比較 while (slowNode != null && !stack.isEmpty()) { if (slowNode.val != stack.peek().val) { return false; } slowNode = slowNode.next; stack.pop(); } return true; }
2、遞迴,遞迴連結串列的前半段,返回以中心位置為軸的對應位置元素的比較結果。
Java程式碼:
/** * 使用遞迴的思路,遞迴連結串列的前一半,返回的時候 * 依次與後半部分對應的節點比較,如果都對應相等 * 則為迴文串,否則不是。 * @param head * @return */ public boolean isPalindrom(Node head) { //得到連結串列的長度,遞迴過程中需要使用 int len = getLindedLength(head); return isPalin(head, len).isPalindrom; } /** * 遞迴求解函式。每次向下遞迴時,傳入的長度減2, * 到達連結串列中間結束遞歸併開始判斷,返回判斷結果。 * 到達中間,即遞迴結束條件有三種情況: * 長度為0:說明連結串列為空 * 長度為1:說明連結串列長度是奇數,此時指向中間節點 * 長度為2:說明連結串列長度為偶數,中間節點有兩個 * 此時指向左邊的中間節點。 * @param head * @param length * @return */ private returnStruct isPalin(Node head, int length) { if (length == 0) {//連結串列為空 returnStruct struct = new returnStruct(); struct.isPalindrom = true; struct.aimNode = null; return struct; } else if (length == 1) {//連結串列長度為奇數 returnStruct struct = new returnStruct(); //中間節點不用判斷,返回它的下一個節點,用於 //遞迴返回後與它的上一個節點比較 struct.isPalindrom = true; struct.aimNode = head.next; return struct; } else if (length == 2){//長度為偶數 //需要比較一下兩個中間節點是否相同,並返回 //靠右的中間節點的下一個節點,用於遞迴返回 //後與靠左的中間節點的上一個節點比較 returnStruct struct = new returnStruct(); struct.isPalindrom = head.val == head.next.val ? true : false; struct.aimNode = head.next.next; return struct; } //拿到返回的比較結果 returnStruct returnStruct = isPalin(head.next, length - 2); if (!returnStruct.isPalindrom) {//如果前面有不符合迴文的節點直接返回false return returnStruct; } //比較當前節點和返回的節點(也就是與當前節點以中心點為軸的對稱節點)是否相同 returnStruct.isPalindrom = head.val == returnStruct.aimNode.val ? true : false; returnStruct.aimNode = returnStruct.aimNode.next; return returnStruct; } /** * 得到指定連結串列的長度 * @param head * @return */ private int getLindedLength(Node head) { if (head == null) { return 0; } Node node = head; int len = 0; while (node != null) { len++; node = node.next; } return len; } /** * 用於包裝遞迴返回值的類。 * 因為遞迴需要返回兩個值,因此使用該類 * 來封裝。 */ class returnStruct { //表示上一次比較是否是迴文串 boolean isPalindrom; Node aimNode;//表示與當前節點對應比較的節點 }
二、求最長迴文子串
這道題是白書中的一道題。題目大意是:輸入一個字串,求其最長迴文子串。判斷時,忽略所有標點符號和空格,忽略大小寫。但是輸出的時候保持原樣。
樣例輸入:Confuciuss say : Madam,I’m Adam.
樣例輸出:Madam,I’m Adam
這道題的思路也很簡單
1、處理字串,過濾標點與空格,並記錄字母在原串中的位置。
2、判斷過濾後的字串是否是迴文。遍歷字串,以當前位置為中心向外擴散,每次擴散長度加一併檢查該子串是否是迴文串。注意子串長度的奇偶性。
Java程式碼:
/**
* 得到最長迴文字串函式。
* 遍歷每個元素,從當前元素開始,依次檢視以自身為中心(分奇偶)的
* 長度遞增的子串是否是迴文字串,並更新當前最長子串的長度和位置
* @param string
* @return
*/
public String getLPS(String string) {
if (string == null) {
return null;
}
if (string.length() == 0) {
return "";
}
char[] strs = string.toCharArray();
//過濾無用自字元,用來存放需要查詢的字元
char[] m = new char[strs.length];
//用來記錄對應位置的字元在原陣列中的位置
int[] p = new int[strs.length];
int mLen = 0;
for (int i = 0; i < strs.length; i++) {
if (Character.isAlphabetic(strs[i])) {
p[mLen] = i;
m[mLen++] = Character.toLowerCase(strs[i]);
}
}
int maxLen = Integer.MIN_VALUE;
int x = 0, y = 0;
//遍歷陣列,以當前字元為中心檢查不同長度的子串是否為迴文串
for (int i = 0; i < mLen; i++) {
//奇數長度的子串
for (int j = 0; i - j >= 0 && i + j < mLen; j++) {
if (m[i - j] != m[i + j]) {
break;
}
if (2 * j + 1 > maxLen) {
maxLen = 2 * j + 1;
x = p[i - j];//記錄最長子串位置
y = p[i + j];
}
}
//偶數長度的子串
for (int j = 0; i - j >= 0 && i + j + 1 < mLen; j++) {
if (m[i - j] != m[i + j + 1]) {
break;
}
if (2 * j + 2 > maxLen) {
maxLen = 2 * j + 2;
x = p[i - j];
y = p[i + j + 1];
}
}
}
StringBuilder build = new StringBuilder();
for (int i = x; i <= y; i++) {
build.append(strs[i]);
}
return build.toString();
}