jsp判断字符串相等_最长回文字符串

题目描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

暴力法

扫描一遍字符串,判断 范围内的字符串是不是回文字符串,并且在这个过程中记录最长的字符串

49a106f5e5efdbc7517cc8ffaff8c3e6.gif
class Solution {
    public String longestPalindrome(String s) {

        if (s == null || s.length() 2) {
            return s;
        }

        char[] str = s.toCharArray();
        int start = 0;
        int maxLength = 1;

        for (int i = 0; i 1; i++) {
            for (int j = i + 1; j                 // 如果 [i,j] 范围的字符串长度小于目前已知的最长回文字符串的长度,则无需判断
                if ((j - i + 1) > maxLength) {
                    // 如果是回文字符串,更新最长回文字符串的长度,以及该回文字符串的起始位置
                    if (isPalindrome(str, i, j)) {
                        maxLength = j - i + 1;
                        start = i;
                    }
                }
            }
        }

        return s.substring(start, start + maxLength);
    }

    // 判断 [start, end] 范围内的字符串是否为回文字符串
    private boolean isPalindrome(char[] str, int start, int end) {
        while (start <= end) {
            // 判断两端的字符串是否相同,不相同则不可能是回文字符串
            if (str[start] != str[end]) {
                return false;
            }
            start++;
            end--;
        }

        return true;
    }
}

动态规划

我们开辟一个数组 , 表示 范围内的字符串是否为回文字符串,那我们可以知道

当 的时候,那么不可能是回文字符串,如果 的时候,如果 是回文字符串,那么 就是回文字符串。

但是如果 的话,这时 没有意义,其实这种情况就是 内只有两个字符,如果 的话,那么它就是一个回文字符串,所以这时可以直接得到 。

1710d6f4480e5fb115a8122cdf6a5027.gif
class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() 2) {
            return s;
        }

        int length = s.length();
        boolean dp[][] = new boolean[length][length];
        for (int i = 0; i             for (int j = 0; j                 dp[i][j] = false;
            }
        }

        // [i,i] 表示单个字符,肯定是回文字符串
        for (int i = 0; i             dp[i][i] = true;
        }

        int maxLength = 1;
        int begin  = 0;
        // 因为 [i, j] 要用到 [i + 1, j - 1],所以让 i 动
        // 如果让 j 动,那么 [i + 1, j -1] 的范围没都判断过,会导致错误的结果
        for (int j = 1; j             for (int i = 0; i                 // 如果字符两端不相等,不是回文字符串
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    // 如果只有两个元素,可以直接判断为回文字符串
                    if (i + 1 > j - 1) {
                        dp[i][j] = true;
                    } else {
                        // 与 dp[i + 1][j - 1] 相同
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 更新最长长度以及起始位置
                if (dp[i][j] && (j - i + 1) > maxLength) {
                    maxLength = j - i + 1;
                    begin = i;
                }
            }
            
        }

        return s.substring(begin, begin + maxLength);
    }
}

中间扩散法

上面的情况都是检查某个范围内的字符串是否是回文字符串,而中心扩散法,则是以某点为中心进行向外扩散,在扩散的同时检查是否是回文字符串,过程如下

  1. 检查该点左右两边的字符串是否相等,如果相等则继续扩散,如果不相等则停止扩散
  2. 停止扩散后记录该回文字符串的长度,如果大于当前记录的最大回文字符串的长度,则进行更新,为了获得该回文字符串,还需要记录该回文字符串的起始位置

需要注意的是,既可以从某个字符开始扩散,也可以从字符间的空隙进行扩散

a9800f809b45cc27838fba252e2cb43a.png

从某个点进行扩散的代码如下:

private int centerSpread(String s, int left, int right) {
    int length = 0;
    while(left >=0 && right         if (!(s.charAt(left) == s.charAt(right))) {
            break;
        }
        length++;
        left--;
        right++;
    }

    return length;
}

如果从某个字符进行扩散,那么 left 和 right 都是该字符的下标,如果从某个字符空隙进行扩散,则 left 和 right 分别为该空隙左右两边字符的下标。扩散的示意图如下

04fc52f75e56e0b6e709aeb6bd7f28ba.gif

完整代码如下

class Solution {
    public String longestPalindrome(String s) {
        int begin = 0;
        int maxLength = 1;

        for (int i = 1; i             // 从字符空隙进行扩散
            int length1 = centerSpread(s, i - 1, i);
            // 从字符进行扩散
            int length2 = centerSpread(s, i, i);

            // 获得二者进行扩散的回文字符串的最大长度
            int length = (2 * length1) >= (2 * length2 - 1) ? 2 * length1 : (2 * length2 - 1);
            // 如果比 maxLength 大,则进行更新
            if (length > maxLength) {
                maxLength = length;
                begin = i - length/2;
            }
        }

        return s.substring(begin, begin + maxLength);
    }

    private int centerSpread(String s, int left, int right) {
        int length = 0;
        while(left >=0 && right             if (!(s.charAt(left) == s.charAt(right))) {
                break;
            }
            length++;
            left--;
            right++;
        }

        return length;
    }
}

Manacher算法

Manacher 算法是对中间扩散法的改进,首先我们在字符间填充 # 以此充当字符空隙,得到一个新的字符串,然后用一个数组记录新字符串每个字符能够扩散多远

b#a#b#a#d

我们复用中心扩散法的代码

class Solution {
    public String longestPalindrome(String s) {
        int begin = 0;
        int maxLength = 1;
        // babad => b#a#b#a#d
        String str = String.join( "#", s.split(""));

        for (int i = 1; i             int length = centerSpread(str, i, i);
            length = 2 * length - 1;
            if (length >= maxLength) {
                // #a# b#b 二者长度相同,但是应选择后面的
                if (length > maxLength || str.charAt(i + length/2) != '#') {
                    maxLength = length;
                    begin = i - length/2;
                }
            }
        }

        // b#a#b => bab
        return String.join( "", str.substring(begin, begin + maxLength).split("#"));
    }

    private int centerSpread(String s, int left, int right) {
        int length = 0;
        while (left >= 0 && right             if (!(s.charAt(left) == s.charAt(right))) {
                break;
            }
            length++;
            left--;
            right++;
        }

        return length;
    }
}

这个时候我们还没有使用上面提到的数组,接下来我们需要利用数组记录一些信息,从而加快中间扩散法的速度。

首先我们要知道如何加快中间扩散法的速度,这就需要利用回文字符串的特点,那就是回文字符串是左右对称的,当我们对回文字符串内的某个字符进行中间扩散时,我们可以先找到对称位置的字符,看看它能够扩散的多远

ec199f7175710726ec68d6fe6650cb0e.png

如果它扩散的距离小于所处回文字符串的范围,那么根据对称的特点,我们不需要对该字符进行中间扩散,它能够扩散的距离就是对称位置所能扩散的距离

3ac6c854d5c00b2f602043f3eb62613f.png

但是对称位置能够扩散的距离刚好在所处回文字符串的边界,或者大于所处的回文字符串,那么此时扩散的距离就不等于对称位置能够扩散的距离,而是需要进行扩散才能确定

95a77edf4cb8e19d7b5ec86f7f1c95da.png

但是我们不需要从该字符串开始进行扩散,因为我们可以确定在回文字符串的范围内,它是对称的,所以我们只需要从回文字符串的外部进行扩散即可

beae847b66789f6b61f7ab34870aff0c.png

扩散完毕后记录能够扩散后的距离,并决定是否更新 begin,maxLength。

现在还有一个问题,如何知道某个字符处于一个回文字符串中,我们通过记录两个变量 center 和 maxRight 来表示一个回文字符串,center 表示回文字符串的中心,maxRight 表示中心离回文字符串右边界的距离

7cca6a0d62392e15ba167804603ae709.png

我们认为如果字符的下标小于 ,那么它就在一个回文字符串中。

那什么时候更新这两个变量呢? 那就是在某个字符扩散之后,他向右边能够扩散的范围大于当前的 ,那么就更新 center 和 maxRight。

0f53f174360abea028607d39a0ae7007.gif

代码如下:

class Solution {
    public String longestPalindrome(String s) {
     // 将字符串转化为 a#b#c#b 的形式
        // String newStr = String.join( "#", s.split(""));
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i             if (i != s.length() - 1) {
                stringBuilder.append(s.charAt(i) + "#");
            } else {
                stringBuilder.append(s.charAt(i));
            }
        }
        String newStr = stringBuilder.toString();

         // records[i] 表示扩散距离
        int[] records = new int[newStr.length()];

        records[0] = 0;
  // 回文字符串的起始位置和长度
        int begin = 0;
        int maxLength = 1;
  // 表示回文字符串
        int center = 0;
        int maxRight = 0;

        for (int i = 1; i       // 如果 str[i] 在某个回文字符串中
            if (i <= center + maxRight) {
      // 对称位置的扩散距离
                int mirrorLength = records[2 * center - i];
      // 超过或等于 center + maxRight 表示的边界,进行中间扩散,并进行必要的更新
                if (i + mirrorLength >= center + maxRight) {
                    // 扩散
                    int length = center + maxRight - i;
                    while (i + length + 1 1) >= 0) {
                        if (newStr.charAt(i + length + 1) != newStr.charAt(i - length - 1)) {
                            break;
                        }
                        length++;
                    }
                    records[i] = length;
                    
                    // 更新
                    if (2 * length + 1 >= maxLength) {
                        if (2 * length + 1 > maxLength || newStr.charAt(center) != '#') {
                            begin = i - length;
                            maxLength = 2 * length + 1;
                        }
                    }

                    if (i + length > center + maxRight) {
                        center = i;
                        maxRight = length;
                    }

                } else {
       // 小于 center + maxRight 形成的边界,直接使用对称位置的值
                    records[i] = mirrorLength;
                }
            } else {
      // 不在回文字符串中,进行中心扩散,并进行必要的更新
                int length = 0;
                while (i + length + 1 1) >= 0) {
                    if (newStr.charAt(i + length + 1) != newStr.charAt(i - length - 1)) {
                        break;
                    }
                    length++;
                }

                if (2 * length + 1 > maxLength) {
                    begin = i - length;
                    maxLength = 2 * length + 1;
                }

                center = i;
                maxRight = length;
                records[i] = length;
            }
        }

     // 将 a#b#b#a 形式的字符串转化为 abba
        // return String.join( "", newStr.substring(begin, begin + maxLength).split("#"));
        String ss = newStr.substring(begin, begin + maxLength);
        StringBuilder stringBuilder1 = new StringBuilder();
        for (int i = 0; i             if (ss.charAt(i) != '#') {
                stringBuilder1.append(ss.charAt(i));
            }
        }

        return stringBuilder1.toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值