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

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

看到一道题没什么思路,首推暴力法,无脑暴力,写出来再说

  • 暴力法,找出每一个子串,并判断是否为回文字符串,注意边界问题
  • 定义一个数组记录找到的最长回文字符串的左右边界
  • 暴力法的时间复杂度为O(n^3),空间复杂度O(1)
public String longestPalindrome(String s) {
		int[] bound = new int[2];
		int n = s.length(), res = 0;
		for (int i = 0; i != n; i++) {
			for (int j = 0; j != n; j++) {
				// 判断此子串是否为回文子串
				boolean f = ifPalindrome(s, i, j);
				if (f && (j - i + 1) > res) {
					res = j - i + 1;
					bound[0] = i;
					bound[1] = j + 1;
				}
			}
		}
		return s.substring(bound[0], bound[1]);

中心法,遍历字符串,把每个字符当做中心,根据条件往两边扩,记录最大值 奇数数组好扩 “aba” 偶数数组不好扩 “abba”,找不到中心,所以要变形

  • "#a#b#b#a#"这样的话中心是第三个# 只记左边长度是4,等价总长 看
  • 一下扩展奇数数组"aba", “#a#b#a#”,b为中心,左边长度是3
  • 变形需要用到StringBuilder吧?
  • 中心法时间复杂度O(n^2) 空间复杂度O(n)
public String longestPalindrome_1(String s) {
		if (s == null || s.length() < 2)
			return s;
		int n = s.length(), res = 0;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i != s.length(); i++) {
			sb.append("#" + s.charAt(i));
		}
		sb.append("#");
		int index = 0;
		s = sb.toString();// 变形后
		for (int i = 0; i != s.length(); i++) {
			// 这里判断能不能往外扩的最长串,返回值要
			int len = caculateLong(s, i);
			// 更新
			if (len > res) {
				res = len;
				index = i;
			}
		}
		/*
		 * substring(a,b); 截取字符串上的距离,左闭右开 [a,b) replace(rex,"要替换的值"); 替换字符串中某些字符
		 */
		return s.substring(index - res, index + res + 1).replace("#", "");
	}
	



public int caculateLong(String s, int i) {// 时间复杂度 O(n)
		int n = s.length(), l = i - 1, r = i + 1, res = 0;
		while (l >= 0 && r < n) {// 最大可以扩到[0,n-1]
			if (s.charAt(l--) == s.charAt(r++)) {
				res++;
			} else
				break;
		}
		return res;
	}

回文中心新解,不用对s进行操作,遍历到i时,考虑i是回文中心(奇数) [i,i+1]的中心为回文中心分别考虑这两种情况,得到各自的回文长度,两者中较大的则是当前i位置能求得的最大回文长度

  • 奇数 “aba”,有回文中心b ,偶数 “abba” 没有回文中心 奇数:把[i,i]传进函数 特殊处理偶数,把[i,i+1]传进判断函数里
  • 1.若s[i]==s[i+1],则进入2,否则退出判断
  • 2.若s[i-1]==s[i+2],则进入3,
  • 最大判断至 s[0] == s[n-1] (整个字符串都为回文字符串), 最后返回当前回文中心回文字符串的长度:r-l-1

无需扩展

public String longestPalindrome_2(String s) {
		if (s == null || s.length() < 1) {
		return "";
	}
	int e = 0;
	int start = 0;
	for (int i = 0; i != s.length(); i++) {
		// 1.考虑i为回文中心
		int len1 = longPalindrom(s, i, i);
		// 2.考虑[i,i+1]之间为回文中心
		int len2 = longPalindrom(s, i, i + 1);
		int len = Math.max(len1, len2); // 最小回文子串也是其自身 1
		if (len > e - start) {// 这里也要抠,本来[e,s]的长度为s-e+1,既然len>e-s,则len最小也等于s-e+1,可以更新一波
			start = i - (len - 1) / 2;// 举例:"aba",b为回文中心,len = 3 , start = 1 - (3-1)/2=0,
			e = i + len / 2;// i/2==(i+1)/2,这里长度是奇数和偶数不影响 end = 1 + 3/2 =2
		}
	}
	return s.substring(start, e + 1);
}

动态规划时间复杂度为O(N2),空间复杂度为O(N^2),

  • 动态规划解法 举例:s=“xabcbay”,其子串"abcba"是回文子串,若x==y,则该串为回文子串
  • dp[i][j]:表示字符串[i,j]是否为回文字符串
  • 动态递推式:dp[i][j] = dp[i+1][j-1] && s[i]==s[j]
  • 最大判断至 s[0] == s[n-1] (整个字符串都为回文字符串), 最后返回当前回文中心回文字符串的长度:r-l-1
public String longestPalindrome_3(String s) {

		// 先填二维数组能填的部分,第一行dp[0][j]
		if (s == null || s.length() < 1)
			return "";
		int n = s.length();
		boolean[][] dp = new boolean[n][n];// 默认复制为false
		int res = 1, start = 0, end = 0;
		// 初始化,任意一个字符i,[i,i]是回文子串, 如果s[i]==s[i+1],则[i,i+1]是回文子串
		for (int i = 0; i != n; i++) {
			for (int j = i; j != n; j++) {
				if (i == j) {
					dp[i][j] = true;
				}
				if ((i + 1) != n && s.charAt(i) == s.charAt(i + 1)) {
					dp[i][i + 1] = true;
				}
			}
		}
		// 数组要从下往上填,因为dp[i][j] 根据 dp[i+1][j-1]来推
		for (int i = n - 2; i >= 0; i--) {
			for (int j = 0; j < n; j++) {
				if (i < n - 1 && j >= 1) {
					if (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]) {
						dp[i][j] = true;
					}
				}
			}
		}
		for (int k = 0; k != n; k++) {
			for (int j = n - 1; j >= k; j--) {
				if (dp[k][j] && res < (j - k + 1)) {
					res = j - k + 1;
					start = k;
					end = j;
				}
			}
		}
		return s.substring(start, end + 1);
	}

总结:对于数组或者字符串求子串的问题,注意不是子序列,这类题可以使用暴力法找到所有的子串,逐一比较,求得解,当然这种解法是不能通过oj的,复杂度太高。可以往动态规划或者滑动窗口上面想。