最长回文子串(longest-palindromic-substring)——动态规划DP

题目

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

示例 1:

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

示例 2:

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

代码

解法 1: 暴力破解

暴力求解,列举所有的子串,判断是否为回文串,保存最长的回文串。

	// 暴力解法
	public String longestPalindrome(String s) {
	    String ans = "";
	    int max = 0;
	    int len = s.length();
	    for (int i = 0; i < len; i++)
	        for (int j = i + 1; j <= len; j++) {
	            String test = s.substring(i, j);
	            if (isPalindromic(test) && test.length() > max) {
	                ans = s.substring(i, j);
	                max = Math.max(max, ans.length());
	            }
	        }
	    return ans;
	}
	
	private boolean isPalindromic(String s) {
		int len = s.length();
		for (int i = 0; i < len / 2; i++) {
			if (s.charAt(i) != s.charAt(len - i - 1)) {
				return false;
			}
		}
		return true;
	}

说明,暴力解法可以优化一下,遍历时只记录begin和maxLen,最后返回时再截取字符串。减少一定的性能消耗。

解法2:中心扩散

  • 枚举所有可能的回文子串的中心位置
  • 中心位置可能是一个字符(奇数回文子串),也有可能是两个相邻的字符(偶数回文子串)。
  • 记录最长回文子串的相关变量

奇数回文子串

在这里插入图片描述

偶数回文子串

在这里插入图片描述

	public String longestPalindrome(String s) {
		int len = s.length();
		if (len < 2) {
			return s;
		}

		int maxLen = 1;
		int begin = 0;

		char[] charArray = s.toCharArray();
		for (int i = 0; i < len - 1; i++) {
			int oddLen = expandAroundCenter(charArray, i, i);//奇数回文子串,比如bab,中心为a
			int evenLen = expandAroundCenter(charArray, i, i + 1);//偶数回文子串,比如baab,中心为aa
			
			int curMaxLen = Math.max(oddLen, evenLen);
			if (curMaxLen > maxLen) {
				maxLen = curMaxLen;
				// 根据maxLen和下标 i 计算出 begin
				// 这一步要在纸上画图发现规律
				begin = i - (maxLen - 1) / 2;
			}
		}
		return s.substring(begin, begin + maxLen);
	}

	/**
	 * 
	 * @param charArray 原始字符串的字符数组
	 * @param left 起始左边界
	 * @param right 起始右边界
	 * @return 回文串的长度
	 */
	private int expandAroundCenter(char[] charArray, int left, int right) {
		//当left = right的时候,回文中心是一个字符,回文中心是一个字符,回文串的长度是奇数
		//当right = left+1的时候,此时回文中心两个字符,回文串的长度是偶数
	    int L = left, R = right;
	    int len = charArray.length;
	    while (L >= 0 && R < len && charArray[L] == charArray[R]) {
	        L--;
	        R++;
	    }
	    //跳出while循环时,恰好满足s.charAt(i) != s.charAt(j),
	    //回文串的长度是 R - L + 1 - 2 = R - L - 1
	    return R - L - 1;
	}

再说一下begin = i - (maxLen - 1) / 2这句。
奇数回文串
在这里插入图片描述
偶数回文串
在这里插入图片描述
公式
在这里插入图片描述
这里 / \mathbf{/} /表示下取整。

解法3 : 动态规划

方法一中,存在大量的重复计算工作,例如当 s=“abcba” 时, 对于子串 “bcb” 和 子串 “abcba”, 分别进行了2次完整的计算,来检测该子串是否是回文串。

很明显的是,对于 s=“abcba” , 在已知 "bcb"是回文串的情况下,要判断 "bcb"是否是回文串的话,只需要判断两边的*位置的字符是否相等即可。 我们定义 P(i,j) 表示 s[i,j]是否是回文串,若s[i,j]是回文串,则P(i,j)=true,否则为false. 则有下面的递推公式成立:

P[i,j] =  p(i+1,j-1) && ( s[i]==s[j] ) 

对于上面公式有2个特殊情况,当子串长度为1或2时,上面公式不成立。我们单独分析这两种情况:

若子串长度为1,即 j==i, 则 P[i,j] = P[i,i] = true; 
若子串长为2,即j==i+1, 则 P[i,j] = P[i,i+1] =  ( s[i]==s[i+1] )

所以

  • 状态:dp[i][j]表示子串s[i..j]是否为回文子串
  • 得到状态转移方程:dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
  • 边界条件:j-1-(i+1)+1<2,整理得j-i<3
  • 初始化:dp[i][i]=true

代码

    public String longestPalindrome(String s) {
		int n = s.length();
		boolean[][] dp = new boolean[n][n];
		String ans = "";
		for (int k = 0; k < n; ++k) {
			for (int i = 0; i + k < n; ++i) {
				int j = i + k;
				if (k == 0) {
					dp[i][j] = true;
				} else if (k == 1) {
					dp[i][j] = (s.charAt(i) == s.charAt(j));
				} else {
					dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]);
				}
				if (dp[i][j] && k + 1 > ans.length()) {
					ans = s.substring(i, i + k + 1);
				}
			}
		}
		return ans;
    }

注意

  • 子串下标为0,子串长度为1,dp[i,i] = true
  • 子串下标为1,子串长度为2,则 dp[i,j] = dp[i,i+1] = ( s[i]==s[i+1] )
  • 子串长度为1,都是True,子串长度为2时,要判断。

解法4:Manacher算法

  • 中心扩散的原理是:
    如果如果当前字符串不是回文串,那以当前字符串为中心的所有串都不是回文串,以此来终止当前字符串的扩散。
  • 动态规划的原理是:
    如果当前字符串首尾字符相同,如果去掉首尾字符剩下的子串不是回文串,那当前字符串肯定不是回文串,以此来终止当前字符串的缩减。
  • 马拉车算法的原理:
    按顺序对每个字符进行中心扩散,利用回文串镜像的特点,找到当前字符在包含它的回文串中的镜像字符,利用镜像字符之前的计算结果,来跳过一些中心扩散的步骤,甚至直接得出结果,以此对中心扩散进行优化。
public String longestPalindrome(String s) {
    if (s == null || s.length() == 0)
        return "";
    // 先拓展,加一个标志位,由于符号任取,和别人一样用 # 吧
    char[] chars = new char[(s.length() << 1) + 1];
    for (int i = 0; i < chars.length; i++)
        chars[i] = (i & 1) == 0 ? '#' : s.charAt((i - 1) >> 1);
    int longestIdx = 1;
    int[] width = new int[chars.length];
    for (int i = 1, mid = 1, right = 1; i < chars.length; i++) {
        // 保证 right 永远大于等于 i,可以保证 i 永远可以对 mid 取到
        // 镜像字符 mirror(至少等于自己)
        if (i > right)
            right = mid = i;
        int left = (mid << 1) - right, mirror = (mid << 1) - i;
        width[i] = Math.min(width[mirror], mirror - left);
        for (int x = i - width[i] - 1, y = i + width[i] + 1; 
             x > -1 && y < chars.length && chars[x] == chars[y]; 
             x--, y++)
            width[i]++;
        longestIdx = width[i] > width[longestIdx] ? i : longestIdx;
        if (i + width[i] > right) {
            right = i + width[i];
            mid = i;
        }
    }
    //  左右必定会扩展到 ‘#’ 的,这样写左边的 # 会对应到原字符串右边的字符,
    // 右边的 # 会对应到原字符串左边的字符。
    int left = (longestIdx - width[longestIdx] + 1) / 2;
    int right = (longestIdx + width[longestIdx] - 1) / 2;
    return s.substring(left, right + 1);
}

资料

最长回文子串(longest-palindromic-substring)
windliang资料
LeetCode 5. Longest Palindromic Substring 最长回文子串 C#
Manacher 算法其实很简单
经典算法问题:最长回文子串之 Manacher 算法

Manacher算法总结

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中求解最长回文子串的方法有多种,其中包括暴力求解和动态规划等。根据引用[1]和引用中的代码示例,我们可以看到两种暴力求解的方法。这些方法通过列举所有可能的子串并判断是否为回文串来找到最长回文子串。 根据引用中的代码示例,我们可以看到一种使用动态规划的方法。这种方法通过构建一个二维数组来保存子串的回文信息,并利用之前计算过的结果来计算新的结果。 根据引用中的代码示例,可以使用以下方法来求解最长回文子串: 1. 定义一个字符串变量`str`,用于存储最长回文子串。 2. 定义一个整数变量`longest`,用于存储最长回文子串的长度。 3. 使用两层循环,外层循环遍历字符串`s`的每一个字符,内层循环从外层循环的字符开始遍历到字符串`s`的最后一个字符。 4. 在内层循环中,使用`substring`方法获取当前子串。 5. 调用`isPalindromes`方法判断当前子串是否为回文串,并且长度大于`longest`。 6. 如果是回文串且长度大于`longest`,则更新`str`和`longest`的值。 7. 返回`str`作为最长回文子串。 根据引用中的代码示例,可以使用以下方法来求解最长回文子串: 1. 定义一个整数变量`len`,用于存储字符串`s`的长度。 2. 如果`len`小于2,直接返回`s`作为最长回文子串。 3. 定义一个整数变量`maxLen`,用于存储最长回文子串的长度。 4. 定义一个整数变量`begin`,用于存储最长回文子串的起始下标。 5. 将字符串`s`转换为字符数组`chars`。 6. 使用两层循环,外层循环遍历字符串中的每一个字符,内层循环从外层循环的字符开始遍历到字符串的最后一个字符。 7. 在内层循环中,判断`chars`数组中从`i`到`j`的子串是否为回文串,并且长度大于`maxLen`。 8. 如果是回文串且长度大于`maxLen`,则更新`maxLen`和`begin`的值。 9. 返回从`begin`到`begin+maxLen`的子串作为最长回文子串。 以上是两种求解最长回文子串的方法,你可以根据实际需求选择其中一种方法进行使用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值