起因
为啥要写这篇博客呢,因为前两天进行了一场面试,就是撕这道题,当时一看,emmm做过的,仔细一看,做过类似的,对就是那个最长回文子串。当时只写了个dfs的,感到耗时应该会很久,但是也没有想出较好的方法,想到动态规划,但是不知道怎么动态规划,所以写下这篇博客
题目以及参考
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。
可以假设 s 的最大长度为 1000
示例:输入bbbab,输出4,因为最长子序列为bbbb
dfs解决
- 按照套路,就先把bbbab转换为#b#b#b#b#a#b#,以解决回文奇偶数的情况
- 中心扩散法,不过要分多种情况,就是这里重复计算
class Solution {
// 最长回文子序列的长度
int res = 1;
public int longestPalindromeSubseq(String s) {
if(s == null || s.length() == 0){
return 0;
}
int len = s.length();
char[] str = new char[2*len + 1];
str[0] = '#';
for(int i = 0;i < len;i++){
str[2*i+1] = s.charAt(i);
str[2*i+2] = '#';
}
// 以上步骤就是将bab变为#b#a#b#
// 该循环为中心扩散的中心点
for(int i = 0;i < str.length;i++){
// 如果为#就表示回文是偶数的情况
// 反之为回文为奇数的情况
if(str[i] == '#'){
dfs(str,i-1,i+1,0);
}else{
dfs(str,i-2,i+2,1);
}
}
return res;
}
/**
* 深搜
* str:改变后的字符串
* le:中心扩散法的左指针
* ri:中心扩散法右指针
* sum:左右指针之间的字符串的最长回文长度
*/
public void dfs(char[] str,int le,int ri,int sum){
// 若指针越界则返回
if(le < 0 || ri >= str.length){
return;
}
// 固定右指针,让左指针向左移动,若相等则计算res以及dfs
for(int k = le;k >= 0;k -= 2){
if(str[k] == str[ri]){
res = Math.max(res,sum+2);
dfs(str,k-2,ri+2,sum+2);
}
}
// 固定左指针,让右指针向右移动,若相等则计算res以及dfs
for(int k = ri;k < str.length;k += 2){
if(str[le] == str[k]){
res = Math.max(res,sum+2);
dfs(str,le-2,k+2,sum+2);
}
}
}
}
按照思路来看,是没问题的,但是重复计算的太多了,果不其然
动态规划
当时没想到,果然下来后还是没想出来,虽说只要关于字符串的问题90%都是动态规划,但是想出转移方程还是需要功底。参考了上述文章以及代码,讲一下思想吧,以下均以i为字符串左边界,j为字符串右边界
- 相同位置(i == j)的长度为1,没啥说的
- 两个不同位置(i与j,i < j)的字符若相同,肯定是在i+1与j-1区间字符串的最长回文子序列长度+2
- 两个不同位置(i与j,i < j)的字符不相同,就是在(i+1,j)与(i,j-1)这个两个区间的字符串中回文子序列最长的那个
- 对于i > j这种情况肯定是为0的
对于上述规则,是从后往前进行dp,利用dp矩阵的上三角,当然也可从前往后dp,则是利用dp矩阵的下三角
class Solution {
public int longestPalindromeSubseq(String s) {
if(s == null || s.length()==0){
return 0;
}
int n = s.length();
int[][] dp = new int[n][n];
for(int i = n-1;i >= 0;i--){
dp[i][i] = 1;
for(int j = i+1;j < n;j++){
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][n-1];
}
}