动态规划有几个典型特征,最优子结构、状态转移方程、边界、重叠子问题。
动态规划核心思想
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。一般解题思路如下:
- 穷举分析
- 确定边界
- 找出规律,确定最优子结构
- 写出状态转移方程
下面通过最长回文子串,学习动态规划。
题目:
给你一个字符串 `s`,找到 `s` 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
**例如:**
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
对于一个字符串而言如果他是回文串那么去掉首尾字符后仍然是一个回文串,所以可以对此进行向下分析。下文我们用P(i,j)表示从i到j的字符串。
边界
子串的长度为1或2。对于长度为1的子串,它显然是个回文串;对于长度为2的子串,只要它的两个字母相同,它就是一个回文串。因此我们就可以写出动态规划的边界条件:
状态转移方程
通过对上面的分析可以得出,只有去掉首尾字符后仍然是一个回文串并且首尾字符相当才能是回文串。可以状态转移方程为:
代码
public static String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
//使用二维数组dp[i][j] 表示 s[i..j] 是否是回文串
Boolean[][] dp = new Boolean[len][len];
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] chars = s.toCharArray();
// 穷举字符串,从2开始
for (int L = 2; L <= len; L++) {
for (int i = 0; i <= len- L; i++) {
// i表示当前开始的字符串的下标
//计算结束的下标
int j = i + L -1;
if (chars[i] != chars[j]){
dp[i][j] = false;
}else {
if (j - i < 3){
//当字符串首尾相等,且长度小于等于3时必定为回文串
dp[i][j] = true;
}else {
dp[i][j] = dp[i+1][j-1];
}
}
// 判断当前是否为回文字串 并确认长度是否最长
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}