## 题目

> - 题号：5
> - 难度：中等
> - https://leetcode-cn.com/problems/longest-palindromic-substring/


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

<b>示例 1</b>：
```c
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
```

<b>示例 2</b>：
```c
输入: "cbbd"
输出: "bb"
```

<b>示例 3</b>：
```c
输入: "a"
输出: "a"
```



---
## 实现

回文是一个正读和反读都相同的字符串，例如，“aba”是回文，而“abc”不是。

<b>第一种：暴力法，列举所有的子串，判断该子串是否为回文</b>。

- 执行结果：超出时间限制

```c
public class Solution
{
    public string LongestPalindrome(string s)
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;
        if (s.Length == 1)
            return s;

        int start = 0;
        int end = 0;
        int len = int.MinValue;
        for (int i = 0; i < s.Length; i++)
        {
            for (int j = i + 1; j < s.Length; j++)
            {
                string str = s.Substring(i, j - i + 1);
                if (isPalindrome(str) && str.Length > len)
                {
                    len = str.Length;
                    start = i;
                    end = j;
                }
            }
        }
        return s.Substring(start, end - start + 1);
    }

    public bool isPalindrome(string s)
    {
        for (int i = 0, len = s.Length / 2; i < len; i++)
        {
            if (s[i] != s[s.Length - 1 - i])
                return false;
        }
        return true;
    }
}
```




<b>第二种：动态规划</b>



动态规划算法与分治法类似，其基本思想也是将待求解问题分解成若干个子问题，先求解子问题，然后从这些子问题的解得到原问题的解。

与分治法不同的是，<u>适合于用动态规划求解的问题，经分解得到子问题往往不是互相独立的（即下一个子阶段的求解是建立在上一个子阶段的解的基础上，进行进一步的求解）</u>。若用分治法来解这类问题，则分解得到的子问题数目太多，有些子问题被重复计算了很多次。如果我们能够<u>保存已解决的子问题的答案，而在需要时再找出已求得的答案，这样就可以避免大量的重复计算，节省时间</u>。

我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到，只要它被计算过，就将其结果填入表中。这就是动态规划法的基本思路。

具体的动态规划算法多种多样，但它们具有相同的<u>填表格式</u>。

使用记号 `s[l, r]` 表示原始字符串的一个子串，`l`、`r` 分别是区间的左右边界的索引值，使用左闭、右闭区间表示左右边界可以取到。

`dp[l, r]` 表示子串 `s[l, r]`（包括区间左右端点）是否构成回文串，是一个二维布尔型数组。

- 当子串只包含 1 个字符，它一定是回文子串；
- 当子串包含 2 个以上字符的时候：`s[l, r]` 是一个回文串，那么这个回文串两边各往里面收缩一个字符（如果可以的话）的子串 `s[l + 1, r - 1]` 也一定是回文串。

故，当 `s[l] == s[r]` 成立的时候，`dp[l, r]`的值由 `dp[l + 1, r - l]` 决定，这里还需要再多考虑一点点：“原字符串去掉左右边界”的子串的边界情况。

- 当原字符串的元素个数为 3 的时候，如果左右边界相等，那么去掉它们以后，只剩下 1 个字符，它一定是回文串，故原字符串也一定是回文串；
- 当原字符串的元素个数为 2 的时候，如果左右边界相等，那么去掉它们以后，只剩下 0 个字符，显然原字符串也一定是回文串。

综上，如果一个字符串的左右边界相等，判断为回文只需以下二者之一成立即可：
- 去掉左右边界以后的字符串不构成区间，即`s[l + 1, r - 1]`包含元素少于2个，即：`r - l <= 2`。
- 去掉左右边界以后的字符串是回文串，即`dp[l + 1, r - 1] == true`。

![](https://img-blog.csdnimg.cn/20200317155934443.png)


**C# 实现**

- 状态：通过
- 103 / 103 个通过测试用例
- 执行用时: 232 ms, 在所有 C# 提交中击败了 46.79% 的用户
- 内存消耗: 40.9 MB, 在所有 C# 提交中击败了 5.43% 的用户


```c
public class Solution {
    public string LongestPalindrome(string s) 
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;
        int len = s.Length;
        if (len == 1)
            return s;
        int longestPalindromelen = 1;
        string longestPalindromeStr = s.Substring(0, 1);
        bool[,] dp = new bool[len, len];

        for (int r = 1; r < len; r++)
        {
            for (int l = 0; l < r; l++)
            {
                if (s[r] == s[l] && (r - l <= 2 || dp[l + 1, r - 1] == true))
                {
                    dp[l, r] = true;
                    if (longestPalindromelen < r - l + 1)
                    {
                        longestPalindromelen = r - l + 1;
                        longestPalindromeStr = s.Substring(l, r - l + 1);
                    }
                }
            }
        }
        return longestPalindromeStr;        
    }
}
```

**Python 实现**
- 执行结果：通过
- 执行用时：3392 ms, 在所有 Python3 提交中击败了 43.87% 的用户
- 内存消耗：21.2 MB, 在所有 Python3 提交中击败了 18.81% 的用户

```python
class Solution:
    def longestPalindrome(self, s: str) -> str:
        count = len(s)
        if count == 0 or count == 1:
            return s
        longestPalindromelen = 1
        longestPalindromeStr = s[0:1]
        dp = [[False] * count for i in range(count)]
        for r in range(1, count):
            for l in range(0, r):
                if s[r] == s[l] and (r - l <= 2 or dp[l + 1][r - 1] == True):
                    dp[l][r] = True
                    if longestPalindromelen < r - l + 1:
                        longestPalindromelen = r - l + 1
                        longestPalindromeStr = s[l:l + longestPalindromelen]
        return longestPalindromeStr
```



---
**参考文献**

- https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

