失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Leetcode——回文子串 / 最长回文子串 / 最长回文子序列

Leetcode——回文子串 / 最长回文子串 / 最长回文子序列

时间:2023-11-26 11:18:13

相关推荐

Leetcode——回文子串 / 最长回文子串 / 最长回文子序列

1. 回文子串

(1)中心扩展

比如对一个字符串 ababa,选择最中间的 a 作为中心点,往两边扩散,第一次扩散发现 left 指向的是 b,right 指向的也是 b,所以是回文串,继续扩散,同理 ababa 也是回文串。这个是确定了一个中心点后的寻找的路径,然后我们只要寻找到所有的中心点,问题就解决了。中心点一共有多少个呢?看起来像是和字符串长度相等,但你会发现,如果是这样,上面的例子永远也搜不到 abab中心点不能只有单个字符构成,还要包括两个字符,比如上面这个子串 abab,就可以有中心点 ba 扩展一次得到,所以最终的中心点由 2 * len - 1 个,分别是 len 个单字符和 len - 1 个双字符。

class Solution{public int countSubstrings(String s) {int ans = 0;for (int center = 0; center < 2 * s.length() - 1; center++) {// left和right指针和中心点的关系是?// 首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)// 大致的关系出来了,可以选择带两个特殊例子进去看看是否满足。int left = center / 2;int right = left + center % 2;while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {ans++;left--;right++;}}return ans;}}

这个解法也同样适用于下面一个题:最长回文子串

(2)动规

状态:dp[i][j] 表示字符串s在[i,j]区间的子串是否是一个回文串。状态转移方程:当 s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1]) 时,dp[i][j]=true,否则为false

解释一下:

当只有一个字符时,比如 a 自然是一个回文串。当有两个字符时,如果是相等的,比如 aa,也是一个回文串。当有三个及以上字符时,比如 ababa 这个字符记作串 1,把两边的 a 去掉,也就是 bab 记作串 2,可以看出只要串2是一个回文串,那么左右各多了一个 a 的串 1 必定也是回文串。所以当 s[i]==s[j] 时,自然要看 dp[i+1][j-1] 是不是一个回文串。

class Solution {public int countSubstrings(String s) {// 动态规划法boolean[][] dp = new boolean[s.length()][s.length()];int ans = 0;for (int j = 0; j < s.length(); j++) {for (int i = 0; i <= j; i++) {//dp[i + 1][j - 1] : 向里面缩if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) {dp[i][j] = true;ans++;}}}return ans;}}

2. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”

输出:“bab”

解释:“aba” 同样是符合题意的答案。

示例 2:

输入:s = “cbbd”

输出:“bb”

示例 3:

输入:s = “a”

输出:“a”

示例 4:

输入:s = “ac”

输出:“a”

提示:

1 <= s.length <= 1000

s 仅由数字和英文字母(大写和/或小写)组成

(1)暴力法

class Solution {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;}public 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;}}

(2)动态规划

dp[i][i]=1; //单个字符是回文串

dp[i][i+1]=1 if s[i]=s[i+1]; //连续两个相同字符是回文串

class Solution {// 动态规划法public String longestPalindrome(String s) {int len = s.length();if (len < 2) {return s;}int maxLen = 1;int begin = 0;char[] cs = s.toCharArray();// dp[i][j]:表示s[i][j]是否是回文串boolean[][] dp = new boolean[len][len];// 初始化:单独一个字符肯定是回文子串for (int i = 0; i < len; i++) {dp[i][i] = true;}// 经验:dp区域是正方形的话,通常左下角区域无效不需要再填,因为走过的区域不用再走for (int j = 1; j < len; j++) {// 上三角区域,按列从上到下填for (int i = 0; i < j; i++) {// 首尾不相等时,必不是回文串if (cs[i] != cs[j]) {dp[i][j] = false;} else {// 首尾相等时,有2种情况// 情况1:s[i...j]长度不超过3,不用检查必为回文串// 情况2:s[i...j]长度大于3,由s[i+1...j-1]来判断dp[i][j] = j - i + 1 <= 3 || dp[i + 1][j - 1];}// 更新max和beginif (dp[i][j] && j - i + 1 > maxLen) {maxLen = j - i + 1;begin = i;}}}return s.substring(begin, begin + maxLen);}}

这样写或许更容易理解(更清晰):

class Solution {public String longestPalindrome(String s) {int len = s.length();if (len < 2)return s;int maxStart = 0; //最长回文串的起点int maxEnd = 0; //最长回文串的终点int maxLen = 1; //最长回文串的长度char[] cs = s.toCharArray();// dp[i][j]:表示s[i][j]是否是回文串boolean[][] dp = new boolean[len][len];for(int right = 1; right < len; right++) {for(int left = 0; left < right; left++) {if (cs[left] == cs[right] && (right - left <= 2 || dp[left + 1][right - 1])) {dp[left][right] = true;// 更新max和beginif (right - left + 1 > maxLen) {maxLen = right - left + 1;maxStart = left;maxEnd = right;}}} }return s.substring(maxStart, maxEnd + 1);}}

(3)中心扩展法

回文中心的两侧互为镜像。因此,回文可以从他的中心展开,并且只有 2n-1 个这样的中心(一个元素为中心的情况有 n 个,两个元素为中心的情况有 n-1 个)

class Solution {public String longestPalindrome(String s) {if (s == null || s.length() < 1){return "";}// 初始化最大回文子串的起点,最大回文子串长度int start = 0;int maxLen = 1;// 遍历每个位置,当做中心位for (int i = 0; i < s.length(); i++) {// 分别拿到奇数偶数的回文子串长度int len_odd = expandCenter(s,i,i);int len_even = expandCenter(s,i,i + 1);// 对比最大的长度int len = Math.max(len_odd,len_even);// i为当前回文的中心:奇数回文为中心下标,偶数回文为一条虚线(i,i + 1)// 计算对应最大回文子串的起点和终点,最长子串长度//如果长度比原本记录的长度大了,说明有更长的回文出现了,所以要记录一下回文的下标if (len > maxLen){// 根据i和maxLen算start下标// 奇数:i-maxLen/2// 偶数:i-maxLen/2+1// 统一:i-(maxLen-1)/2//这里为什么要i-1?,这里说明一下,因为for循环是从0开始的,//如果是奇数回文,假设有个回文是3个,那么len=3,此时中心i是下标1(从0开始),那么(len-1)/2和len/2的结果都是1,因为整型会向下取整//但是如果是偶数回文,假设有个回文是4个,那么len=4,此时的中心是一条虚线,但是i的位置却在1,(因为S是从左向右遍历的,如果从右向左,//i的位置就会在2.)这时候,(len-1)/2=1,len/2=2.很明显为了保证下标正确,我们需要的是(len-1)/2.原因其实是i在中心线的左边一位,//所以要少减个1.maxLen = len;start = i - (len - 1)/2;}}return s.substring(start, start + maxLen);//s.substring(3,5) 切割 [3,5),左闭右开}private int expandCenter(String s,int left,int right){// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数// 跳出循环的时候恰好满足 s.charAt(left) != s.charAt(right)while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){left--;right++;}//这里其实是right-left+1-2,意思就是right-left+1是本来的长度,但是由于上面最后一次判断肯定false,所以最后一次left--和right++//其实不属于回文的一部分,所以要减去2return right - left - 1;}}

简化一下代码:

class Solution {public String longestPalindrome(String s) {if (s == null || s.length() < 1){return "";}String result = "";int len = s.length();for (int center = 0; center < len * 2 - 1; center++) {int left = center / 2;int right = left + center % 2;while (left >= 0 && right < len && s.charAt(left) == s.charAt(right)) {String tmp = s.substring(left, right + 1);if (tmp.length() > result.length())result = tmp;left--;right++;}}return result;}}

3. 最长回文子序列

(1)动规

状态:

dp[i][j] 表示 s 的第 i 个字符到第 j 个字符组成的子串中,最长的回文序列长度是多少。

转移方程:

如果 s 的第 i 个字符和第 j 个字符相同的话: dp[i][j] = dp[i + 1][j - 1] + 2如果 s 的第 i 个字符和第 j 个字符不同的话: dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])然后注意遍历顺序,i 从最后一个字符开始往前遍历,j 从 i + 1 开始往后遍历,这样可以保证每个子问题都已经算好了。 从递推公式dp[i][j] = dp[i + 1][j - 1] + 2 和 dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) 可以看出,dp[i][j]是依赖于dp[i + 1][j - 1] 和 dp[i + 1][j],如果我们想求dp[i][j],那么其他3个必须都是已知的,很明显从上往下遍历是不行的,我们只能让i从最后一个字符往前遍历,j从i的下一个开始遍历,最后只需要返回dp[0][length - 1]即可。

初始化:

dp[i][i] = 1 单个字符的最长回文序列是 1

class Solution {public int longestPalindromeSubseq(String s) {int len = s.length();int[][] dp = new int[len][len];//这里i要从最后一个开始遍历for(int i = len-1; i >= 0; --i) {for(int j = i; j < len; ++j) {//单个字符也是一个回文串if(i == j) {dp[i][j] = 1;continue;} //j从i的下一个开始if(s.charAt(i) == s.charAt(j)) {dp[i][j] = dp[i+1][j-1] + 2;} else {dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);}} }return dp[0][len-1];}}

如果觉得《Leetcode——回文子串 / 最长回文子串 / 最长回文子序列》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。