写在最前:
最近刷的几个题都是可以用DP来处理字符串的问题,因为处理思想比较相似,因此一并整理出来供自己反思和大家参考交流。
上一篇是用DP来处理优化编辑的问题,本篇是求解最长字符字串问题。
题目来源:
牛客网
leetcode
上面两个链接里的题目都是求最长回文子串的题,只是牛客网的题目要求返回最长字串长度,而力扣的要求返回最长字串(存在多解的情况,返回一个即可)。
求解难度上两种返回结果相同,不过存储难度的力扣的题目比牛客的题目更需要那么一丢丢技术含量,因此此篇博客的程序是基于力扣网的题目。
题目详情:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
所谓回文串,指左右对称的字符串。
所谓子串,指一个字符串删掉其部分前缀和后缀(也可以不删)的字符串
(注意:记得加上while处理多个测试用例)
输入描述:
输入一个最大长度为1000的字符串
输出描述:
返回s中的最长回文子串
示例1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例2:
输入: "cbbd"
输出: "bb"
解题思路:
首先我们要知道一个重要的回文串的性质,即:
性质一:
对于一个长度大于2的回文子串而言,如果将其首位的两个字母去除之后,它依旧是个回文串。
参考以上性质,我们顺便可以发现一个和本题没有关系但也很重要的回文串的性质:
性质二:
回文串中至多只会有一种字符出现奇数次,其余种字符均出现偶数次 。
因此回文串分为以下两种情况:
一:存在1种字符出现次数为奇数。例:"aba"
,其中‘a’
出现2次,'b'
出现1次
二:所有字符出现的次数均为偶数。例:"abba"
,其中‘a’
出现2次,'b'
出现2次
构建状态转移方程
根据这个性质,我们可以用动态规划的思想来解决本题,首先来找该本问题的状态转移方程。
像上次的优化编辑问题问题一样,因为是两个字符串的改变导致状态发生的转移,因此同样可以先定义一个二维数组dp[i][j]
表示输入字符串s
的第i+1
个字符s[i]
到j+1
个字符s[j]
组成的字符串(以后用s[i:j]
表示)是否为回文串,是的话可以令其为1,否则令其为0,拿示例1中的"babad"
举例,dp[1][3]
表示第2位字符即s[1]
到第4位字符即s[3]
组成的字符串即"aba"
是否为回文串,因为"aba"
是回文串,因此dp[1][3]=1
。
因此依据回文串的性质一,我们可以用每次往前一个字串上后各增加一个字符的方式来寻找最大的回文字串。
于是可以得到状态转移方程:
d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] a n d ( s [ i ] = = s [ j ] ) dp[i][j] =dp[i+1][j-1] and (s[i]==s[j]) dp[i][j]=dp[i+1][j−1]and(s[i]==s[j])
上面方程的含义即为:
当且仅当s[i+1:j-1]
是回文串并且s[i]=s[j]
时,s[i:j]
才是回文串。
边界问题
因为性质一是基于长度大于2个字符串,因此我们需要考虑长度小等于2的字符串,对于这种字符串我们只需在遍历的时候直接比较s[i]==s[j]
即可,因为该种情况无法再去头去尾,因此不存在状态转移。
考虑到边界问题最后总的状态转移方程为:
d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] & & s [ i ] = = s [ j ] , 长 度 > 2 s [ i ] = = s [ j ] , o t h e r s dp[i][j]=\left\{ \begin{aligned} &dp[i+1][j-1] \quad \&\& \quad s[i]==s[j] ,& 长度>2\\ &s[i]==s[j] , & others\\ \end{aligned} \right. dp[i][j]={ dp[i+1][j−1]&&s[i]==s[j],s[i]==s[j],长度>2others
因此我们可以用一个两层循环暴力遍历输入的字符串。
完整的程序实现输入:
C语言
#include
#include
#include
#define MAXSIZE 1001
int main(){
char str[MAXSIZE] = {
'\0'};
while(scanf("%s", str) != EOF){
int i, j, l, len = strlen(str);
int dp[MAXSIZE][MAXSIZE] = {
0};
char *res = (char*)calloc((len+1), sizeof(char));
for(l=0; l<len; l++){
for(i=0; i+l<len; i++){
j = l + i;
dp[i][j] = (str[i] == str[j]);
if(l>1)
dp[i][j] = (dp[i+1][j-1] && (str[i] == str[j]));
if(dp[i][j] && l+1 >strlen(res)){
strncpy(res, &s[i], l+1); //复制满足条件的字串
res[l+1] = '\0';
//因为strncpy函数不会在结尾添加字符串结尾符,因此需手动添加
}
}
}
}
return 0;
}
Python版
def longestPalindrome(s):
n = len(s)
dp = [[False] * n for _ in range(n)]
res = ""
# 枚举子串的长度 l+1
for l in range(n):
# 枚举子串的起始位置 i,这样可以通过 j=i+l 得到子串的结束位置
for i in range(n-l):
j = i + l
dp[i][j] = s[i] == s[j]
if(l>1):
dp[i][j] = (dp[i + 1][j - 1] and s[i] == s[j])
if dp[i][j] and l + 1 > len(res):
res = s[i:j+1]
return res
while True:
try:
s=input()
if s:
print(longestPalindrome(s))
except:
break
如果是牛客网的题目,则维护一个整形变量,当s[i:j]
是回文串并且长度大于之前的长度时(即最后一个if判断)更细即可,最后返回这个整形变量即为答案。
写在最后
如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。
如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。
手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。
以上。