1. 程式人生 > >判斷連結串列是否為迴文串以及關於迴文串問題的討論

判斷連結串列是否為迴文串以及關於迴文串問題的討論

最近在看程式設計師面試金典,在連結串列部分看到有一題問如何判斷連結串列是否是迴文串,然後想到白書中也有對最長迴文子串的討論,故想做一點總結。

一、判斷連結串列是否為迴文串

連結串列的資料結構是這樣子滴:

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();
}