5. Longest Palindromic Substring

最长回文子串算法解析
本文深入探讨了寻找字符串中最长回文子串的多种算法,包括中心扩散法、动态规划、Manacher算法和后缀树。通过对比不同方法的时间和空间复杂度,帮助读者理解每种算法的特点及适用场景。

题目

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example :

Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.

我的想法

从中间向外扩散,使用半径来扩散,只考虑到了奇数子串的对称。解决这个问题可以参考下面Manacher算法

class Solution {
    public String longestPalindrome(String s) {
        if(s.length() == 0 ||s.length() == 1) return s;
        int center = 1, lenMax = 0;
        
        for(int i = 1; i < s.length()-1; i++){
            int radius = 1, length = 0;
            while(i - radius >= 0 && i + radius < s.length()){
                if(s.charAt(i - radius) == s.charAt(i + radius)){
                    length = radius;
                    radius ++;
                }
                else{
                    radius--;
                    length = radius;
                    break;
                }
            }
            if(length > lenMax){
                center = i;
                lenMax = length;
            }
        }
        
        return s.substring(center-lenMax, center+lenMax);
    }
}

2019.9.13 update: 再刷这道题时能想起可以用中心线和dp的方法来做,但是中心线却怎么也写不出来。所以还是不熟练,没有吃透

解答

leetcode solution 1: Expand Around Center
也是从中间向外扩散,用边界进行扩散,兼顾了奇数和偶数子串。时间复杂度为n*n
2019.9.13 update: 外层循环控制的是中心线!!!不是起点!!!!换个名字更好记忆,枚举中心点。这里特别还需注意头和尾的表达方式

public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);//奇数,中心为一个确切的数
        int len2 = expandAroundCenter(s, i, i + 1);//偶数,中心在两个数之间
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2; //特别注意头尾的换算方法
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
    int L = left, R = right;
    while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
        L--;
        R++;
    }
    return R - L - 1;  //(R-1)-(L+1)+1 循环结束后的R和L已经改变,需要改回来
}

leetcode solution 2: Dynamic Programming
判断dp[i][j]需先判断dp[i+1][j-1]是否为true,再判断s.i==s.j
性能没有方法一好

public String longestPalindrome(String s) {
  int n = s.length();
  String res = null;
    
  boolean[][] dp = new boolean[n][n];
  //2019.9.13update: 需要注意,因为这里也是bottom-up,没有用递归
  //因此必须从后往前循环,这样才能保证前项的cases都已经计算完成
  for (int i = n - 1; i >= 0; i--) {
    for (int j = i; j < n; j++) {
      dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1]);
            
      if (dp[i][j] && (res == null || j - i + 1 > res.length())) {
        res = s.substring(i, j + 1);
      }
    }
  }
    
  return res;
}

2019.9.13update: 第二种写法bottom-up,应该比第一种更加清晰。外层为子串长度,内层循环起始数字

    public String longestPalindrome(String s) {
        // write your code here
        if(s == null || s.length() <= 1) return s;
        
        boolean[][] dp = new boolean[s.length()][s.length()];
        int start = 0, end = 0;
        for(int len = 0; len < s.length(); len++){
            for(int i = 0; i + len < s.length(); i++) {
                if((len < 2 || dp[i + 1][i + len - 1]) && s.charAt(i) == s.charAt(i + len)) {
                    dp[i][i + len] = true;
                    if(len > end - start) {
                        start = i;
                        end = i + len;
                    }
                }
            }
        }
        return s.substring(start, end+1); //注意substring第一个参数inclusive,第二个exclusive
    }

leetcode solution 3: Manacher’s Algorithm
通过补符号使得字符串永为奇数串
bob --> #b#o#b#
noon --> #n#o#o#n#

class Solution {  
	public String longestPalindrome(String s) {
		char[] c = new char[s.length() * 2 + 3];
		c[0] = '^';
		c[1] = '@';
		for (int i = 0; i < s.length(); i++) {
			c[2 + i * 2] = s.charAt(i);
			c[2 + i * 2 + 1] = '@';
		}
		c[c.length - 1] = '$';

		int[] length = new int[c.length];
		int center = 0, right = 0, maxLeft = 0, maxLength = 0;
		for (int i = 2; i < c.length - 2; i++) {
			if (i < right) length[i] = Math.min(right - i, length[2 * center - i]);
			while (c[i - length[i] - 1] == c[i + length[i] + 1]) length[i]++;
			if (i > right) {
				center = i;
				right = i + length[i];
			}
			if (length[i] > maxLength) {
				maxLength = length[i];
				maxLeft = (i - length[i]) / 2;
			} 
		}
		return s.substring(maxLeft, maxLeft + maxLength);
	}
}

leetcode solution 4: Suffix Tree
好难,之后学到树再补吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值