动态规划

1.最长回文子串

在这里插入图片描述

分析

一个回文去掉两头以后,剩下的部分依然是回文

  • 如果一个字符串的头尾两个字符都不相等,则这个字符串一定不是回文串;
  • 如果一个字符串的头尾两个字符相等,才有必要继续判断下去:
    • 如果里面的子串是回文,整体就是回文;
    • 如果里面的子串不是回文,整体就不是回文;

即:在头尾字符相等的情况下,里面子串的回文性质决定了整个子串的回文性质,这就是状态转移。因此可以把状态定义为原字符串的一个子串是否为回文子串。

第一步:定义状态

  • dp[i][j] 表示子串 s[i…j] 是否为回文子串,这里子串 s[i…j] 定义为左闭右闭区间,可以取到 s[i] 和 s[j]。

第二步:思考状态转移方程

  • 在这一步分类讨论(根据头尾字符是否相等),根据上面的分析得到:

dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]

说明:

  • 「动态规划」事实上是在填一张二维表格,由于构成子串,因此 i 和 j 的关系是 i <= j ,因此,只需要填这张表格对角线以上的部分。
  • 看到 dp[i + 1][j - 1] 就得考虑边界情况。

边界条件是:表达式 [i + 1,j - 1] 不构成区间,即长度严格小于2,即 j - 1 - (i + 1) + 1 < 2,得 j - i < 3。

这个结论很显然: j - i < 3 等价于 j - i + 1 < 4,即当子串 s[i…j] 的长度等于2或者等于3时,其实只需要判断一下头尾两个字符是否相等就可以直接下结论了。

  • 如果子串 s[i + 1…j - 1] 只有1个字符,即去掉两头,剩下中间部分只有1个字符,显然是回文;
  • 如果子串 s[i + 1…j - 1] 为空串,那么子串 s[i,j] 一定是回文子串。

因此,在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下结论,dp[i][j] = true,否则才执行状态转移。

第三步:考虑初始化

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

事实上,初始化的部分都可以省去,因为只有一个字符的时候一定是回文,dp[i][i] 根本不会被其他状态值所参考。

第四步:考虑输出

只要一得到 dp[i][j] = true,就记录子串的长度和起始位置,没有必要截取,这是因为截取字符串也要消耗性能,记录此时的回文子串的「起始位置」和「回文长度」即可。

public class Solution {
   
    public 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];
		char[] ch = s.toCharArray();

		for(int i = 0; i < len; i++) dp[i][i] = true;
		for(int j = 1; j < len; j++) {
   
			for(int i = 0; i < j; i++) {
   
				if(ch[i] != ch[j]) dp[i][j] = false; //前后两元素不相同时
				else {
    //前后两元素相同时
					if(j - i < 3) dp[i][j] = true; //此时子串长度小于3,肯定为回文
					else dp[i][j] = dp[i + 1][j - 1]; //否则看减去首尾字符后的子串是否为回文
				}
				//只要dp[i][j]==true成立,就表示子串s[i..j]是回文,并与之前的回文子串的长度比较
				//若大于,则记录此时的回文长度和起始位置
				if(dp[i][j] && j - i + 1 > maxLen) {
   
					maxLen = j - i + 1;
					begin = i;
				}
			}
		}
		return s.substring(begin, begin + maxLen);
    }
}

举个例子,以 ‘babad’ 为例:
在这里插入图片描述
1、当 i 和 j 的差距等于小于3的时候,dp值可以直接判断,不用参考以前的dp值;
2、其它情况,每当计算新dp值的时候,都一定会参考「左下角」的dp值,即 dp[i + 1][j - 1](i + 1 表示在下边,j - 1 表示在左边)。

原文地址

————————————————————————————————————————

2.不同路径

在这里插入图片描述
在这里插入图片描述

分析

在这里插入图片描述

这里机器人智能向下和向右移动,用 dp[i][j] 表示到坐标(i,j)这个格内有多少条不同的路径,所以最终答案就是求 dp[m-1][n-1] 。

递推公式是: dp[i][j]=dp[i-1][j]+dp[i][j-1]

  • dp[i-1][j]表示的是从上面走过来的路径条数。
  • dp[i][j-1]表示的是从左边走过来的路径条数。

那么边界条件是什么呢?如果终点在第一行的任何位置都只有一条路径,同理终点在第一列的任何位置也都只有一条路径,所以边界条件是第一行和第一列都是1

class Solution {
   
    public int uniquePaths(int m, int n) {
   
		int[][] dp = new int[m][n];
        for(int i = 0; i < n; i++) {
   
        	dp[0][i] = 1; //第一行都是1
         }
        for(int i = 0; i < m; i++) {
   
            dp[i][0] = 1; //第一列都是1
        }
        for(int i = 1; i < m; i++) {
   
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值