【LeetCode】回文串专题

目录

【LeetCode】5.最长回文子串

【LeetCode】680. 验证回文字符串 Ⅱ


【LeetCode】5.最长回文子串

题目链接

https://leetcode-cn.com/problems/longest-palindromic-substring/

题目描述

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

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

示例 2:
输入: "cbbd"
输出: "bb"

解题思路

(1)暴力求解

字符串问题,首先就是想到暴力的方法。

找出字符串s的所有子串,并对所有子串进行判断其是否为回文串,如果字符串s有多个不同长度的回文子串,则用ans以及maxx迭代更新最长回文子串以及最长回文子串的长度。时间复杂度为O(n3),基本上是超时的。

(2)中心扩展法

思路:

  • 长度为奇数的回文子串中心有一个元素,例如 "aba" 的中心元素为 "b" ;

  • 长度为偶数的回文子串中心有两个元素,例如 "abba" 的中心元素为 "bb" 。

  • 分别以两种中心为基础寻找并比较出最长回文子串。

(3)动态规划(本题的解法与LCS类型)

动态规划一般也只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

动态规划算法分以下4个步骤:

(1)描述最优解的结构
(2)递归定义最优解的值
(3)按自底向上的方式计算最优解的值   //此3步构成动态规划解的基础。4)由计算出的结果构造一个最优解。   //此步如果只要求计算最优解的值时,可省略。
好,接下来,咱们讨论适合采用动态规划方法的最优化问题的俩个要素:最优子结构性质,和子问题重叠性质。

(1)最优子结构
    如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。意思就是,总问题包含很多个子问题,而这些子问题的解也是最优的。

(2)重叠子问题
    子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

该问题满足最优子结构性质:一个最长回文串去掉两头以后,剩下的部分依然是最长回文串。

第一步:定义状态

dp[i][j]:表示子串s[i,j]是否为回文子串。

当dp[i][j] = 1:表示子串s[i,j]为回文子串,反之。

第二步:状态转移方程

这一步在做分类讨论(根据头尾字符是否相等)。

 if j-i >= 2

dp[i][j] = 1, if dp[i+1][j-1] == 1 && s[i] == s[j]

dp[i][j] = 0, if dp[i+1][j-1] == 0 || s[i] != s[j]

else

dp[i][j] = 1,if s[i] == s[j]

dp[i][j] = 0,if s[i] != s[j]

Q:j-i >= 2这个条件是如何求解出来的?

A:因为dp[i][j]表示子串s[i,j]是否为回文子串,所以i<=j,同理dp[i+1][j-1]中,i+1 <= j-1,整理得j - i <= 2.

第三步:考虑初始化

初始化的时候,单个字符一定是回文串,因此把对角线先初始化为 1,即 dp[i][i] = 1 。

第四步:考虑输出

只要一得到dp[i][j] = 1,就记录子串的长度和起始位置,没必要利用substr函数截取,因为截取字符串也要消耗性能,记录此时的回文子串的“起始位置”和“回文长度”即可。

第五步:考虑状态是否可以压缩
因为在填表的过程中,只参考了左下方的数值。事实上可以压缩,但会增加一些判断语句,增加代码编写和理解的难度,丢失可读性。在这里不做状态压缩。

(4)观察法

观察子串,回文字数要么是偶数个,那么类似cbaabc这种中间是aa的;要么是奇数个,类似是cbabc中间是bab型的,仅此两种。所以用for先找偶数型(aa)回文,找s[i]=s[i+1],找到后再往两边推,直到s[i-len]与s[i+1+len]不等为止,记录此子串的位置和长度,最后进行长度比较。再找s[i]=s[i+2],即找奇数型(aba)回文,找到后处理同上。

AC代码

(2) 中心扩展法

 1 class Solution {
 2     public String longestPalindrome(String s) {
 3         if (s.isEmpty()) {
 4             return "";
 5         }
 6         // 记录最长回文子串开始和结束的位置。
 7         int start = 0, end = 0;
 8         for (int i = 0; i < s.length(); i++) {
 9             // 查找以当前元素为中心的最长回文子串长度。
10             int len1 = function(s, i, i);
11             // 查找以当前元素和下一个元素为中心的最长回文子串长度。
12             int len2 = function(s, i, i + 1);
13             int maxLen = Math.max(len1, len2);
14             // 判断记录遍历过位置能找到的最长回文子串起始位置。
15             if (maxLen > end - start) {
16                 start = i - (maxLen - 1) / 2;
17                 end = i + maxLen / 2;
18             }
19         }
20         return s.substring(start, end + 1);
21     }
22 
23     private int function(String s, int left, int right) {
24         // 以传入的相邻两位置为中心找最大回文子串长度,两位置相同表示一个元素为中心。
25         while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
26             left--;
27             right++;
28         }
29         return right - left - 1;
30     }
31 }

(3)动态规划法

 1 class Solution {
 2 public:
 3     string longestPalindrome(string s) {
 4         if(s.size() == 0 || s.size() == 1 || (s.size() == 2 && s[0] == s[1])) return s;
 5         vector<vector<int> > dp(s.size(),vector<int>(s.size())); //二维vector数组的初始化方式
 6         int index = 0;
 7         int len = 0;
 8         for(int i = 0; i < s.size(); i++) dp[i][i]=1;
 9         for(int j = 1; j < s.size(); j++)
10         {
11             for(int i = 0; i < j; i++)
12             {
13                 if(j - i < 2)
14                 {
15                     if(s[i] == s[j]) dp[i][j] = 1; //dp[i][j]=1表示字符串s[i]-s[j]为回文串,反之。
16                     else dp[i][j] = 0;
17                 }
18                 else
19                 {
20                     if(dp[i+1][j-1] == 1 && s[i] == s[j]) dp[i][j] = 1;
21                     else dp[i][j] = 0;
22                 }
23                 
24                 if(dp[i][j] == 1 && j-i+1>len) //迭代更新,利用len记录回文串的最大长度
25                 {
26                     index = i;
27                     len = j-i+1;
28                 }
29             }
30         }
31         if(len == 0) return s.substr(0,1);
32         else return s.substr(index,len);
33     }
34 };

(4)观察法

 1 class Solution {
 2 public:
 3 string longestPalindrome(string s) 
 4 {
 5     const int slen = s.length();
 6     int i,t;
 7     int max = 0;
 8     t = 0;
 9     for (i = 0; i < slen; i++) //该for循环判断cbaabc类型的回文串
10     {
11         if(s[i]==s[i+1])
12             {
13                 int hlen=1;
14                 while((i-hlen>=0) && (i+1+hlen<=slen-1)&&(s[i-hlen]==s[i+1+hlen]))
15                     hlen++;
16                 if (2 * hlen > max)
17                 {
18                     max = 2 * hlen;
19                     t = i-hlen+1;
20                 }
21             }
22     }
23     for (i = 0; i < slen-1; i++)//该for循环判断cbabc类型的回文串
24     {
25         if (s[i] == s[i + 2])
26         {
27             int hlen = 1;
28             while ((i - hlen >= 0 //防止数组下标越界) && (i + 2 + hlen <= slen-1//防止数组下标越界) && (s[i - hlen] == s[i + 2 + hlen]))
29                 hlen++;
30             if (2 * hlen + 1 > max)
31             {
32                 max = 2 * hlen + 1;
33                 t = i-hlen+1;
34             }
35         }
36     }
37     if (max==0)
38         return s.substr(0,1);
39     else
40         return s.substr(t, max);
41 }
42 };

【LeetCode】680. 验证回文字符串 Ⅱ

题目链接

https://leetcode-cn.com/problems/valid-palindrome-ii/

题目描述

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1:
输入: "aba"
输出: True
示例
2: 输入: "abca" 输出: True 解释: 你可以删除c字符。
注意: 字符串只包含从 a
-z 的小写字母。字符串的最大长度是50000。

解题思路

1.暴力枚举

先判断字符串S是否为回文字符串,如果是则返回true,如果不是按顺序删除字符串S中的每一个字符,然后判断剩余的字符串是否为回文字符串即可。

2.双指针

当检查到前后不一样的字符,必须要删掉一个(要么开头,要么结尾)。删掉之后检查剩下的字符串是否为回文。是则返回TRUE,反之。

AC代码

双指针

 1 class Solution {
 2 public:
 3     bool ispalindrome(string s) //判读字符串S是否为回文字符串,这样的时间复杂度为O(n/2)
 4     {
 5         int start = 0;
 6         int end = s.size()-1;
 7         while(start <= end)
 8         {
 9             if(s[start] != s[end]) return false;
10             start++;
11             end--;
12         }
13         return true;
14     }
15     bool validPalindrome(string s) {
16         int start = 0;
17         int end = s.size()-1;
18         if(s.size()<=2) return true;
19         while(start <= end)
20         {
21             if(s[start] == s[end])
22             {
23                 start++;
24                 end--;
25             }
26             else
27             {
28                 int len = end-start;
29                 string temp = s.substr(start+1,len);//当两个元素不相同时,删除前面一个元素,判断剩下的元素是否为回文串
30                 if(ispalindrome(temp)) return true;
31                 string te = s.substr(start,len);//当两个元素不相同时,删除后面一个元素,判断剩下的元素是否为回文串
32                 if(ispalindrome(te)) return true;
33                 return false;
34             }
35         }
36         return true;
37     }
38 };
原文地址:https://www.cnblogs.com/XDU-Lakers/p/12889836.html