动态规划经典算法之最长回文子序列问题

开篇

好吧我承认我又懒了,KMP算法是一个比较复杂的算法,如果用动态规划来解决更不是一句两句话说得清楚的,所以周四没课那天更新KMP算法动态规划解决的文章。今天聊点不复杂且和前文有直接关系的——动态规划之子序列问题解题模板
我们知道,子序列问题比子串问题要难,因为子串问题是连续的,而子序列问题是不连续的,我们用暴力法都不一定可以解决。一般题目不会让我求最短子序列,因为最短子序列肯定是一个字符啊,没有比它更短的了。所以一般都是最长子序列问题。那最长子序列问题暴力法无法解决,而且还有最值要求,我们很自然地想到了动态规划算法,时间复杂度为O(n^2)。(也有优化的方法,比如二分法优化单调最长子序列,时间复杂度为O(nlogn))

模板介绍

解决这种子序列问题一般我们有两种思路,这两种思路都是针对dp table的定义的
1.使用1维的dp table

int n = array.size();
int dp[n] = new int[n];
for(int i = 1;i < n;i++)
{
	for(int j = 0;j < i;i++)
		dp[i] = 最值(dp[i],dp[j]+.....);
}

2.使用2维的dp table

int n = array.size();
int dp[n][n] = new int[n][n];
for(int i = 0;i < n;i++)
{
	for(int j = 0;j < n;j++)
	{
		if(array[i] == array[j])
			dp[i][j] = dp[i][j]+....;
		else
			dp[i][j] = 最值(....)
	}
}

我们今天要聊的这个最长回文子序列问题就是这么一个模板,但是这种模板还需要分成不同的情况:
2.1涉及两个字符串/数组(最长公共子序列)
dp[i][j]表示arr1[0…i]和arr2[0…j]的最长公共子序列长度。
2.2只涉及一个字符串/数组(比如最长回文子序列)
dp数组含义如下:在子数组array[i…j]中,我们要求的最长回文子序列长度。

问题描述

OK,介绍完了基本的模板我们开始解决实际问题——最长回文子序列
问题介绍很简单,我们只需要找出字符串中最长的回文子序列即可,而解题模板就是刚刚介绍的2维dp table单字符串情形
在这里插入图片描述
状态转移方程
我们通过这个图来理解一下状态转移方程,找状态转移方程需要归纳思维,说白了就是如何从已知的结果推出未知的部分。
所以我们来思考一下状态转移方程应该如何写呢?
1.如果s[i]==s[j]
如果当前的s[j]与s[j]相等了,那么dp[i][j]应该通过上一个可能构成回文子序列的序列推导出,s[i…j]之前一个可能构成回文子序列的序列是什么呢?
既然可以构成回文子序列,那么一定是s[i]=s[j],s[i+1]=s[j-1]…,所以上一个回文子序列的长度应该为dp[i+1][j-1].
所以状态转移方程为dp[i][j] = dp[i+1][j-1]+2
2.如果s[i]!=s[j]
s[i]!=s[j]的时候我们思路是一样的,找出上一个可能是回文子序列的序列。已知s[i]!=s[j],则上一段序列我们应该看s[i…j-1]这一段或者s[i+1…j]这一段。
所以状态转移方程为dp[i][j] = max(dp[i+1][j],dp[i][j-1])
在这里插入图片描述
base case
明确了状态转移方程其实问题就解决了一大半了,下面就是base case了。
我们要时刻记住,我们的研究对象是一个字符串,对于字符串来说会有什么极端情况(base case)呢?那就是当i>=j的时候。
如果i==j
那么我们就是要找s[i…i]的最长回文子序列,答案可想而知,就是1
如果i>j
那么我们就是要找s[i…j]的最长回文子序列,根本不存在这样的序列不是吗?所以答案是0.

代码

动态规划问题,明确了状态转移方程,明确了base case,知道了如何做选择,那么即使你没写代码,这个问题你其实也解决了。但是在这里我们还是给出代码。

我们可以发现dp[i][j]的来源一共有三种可能:dp[i+1][j-1],dp[i+1][j],dp[i][j-1]

所以我们在二维dp table上可以画出它的方向:
在这里插入图片描述
这里的0和1,是我们的base case。可以看出,我们只有明确了左,左下,和下才能确定当前dp值,所以由图可知,正向遍历并不能得到答案,所以我们可以斜着遍历或者反向遍历:
在这里插入图片描述
这里我选择反向遍历,即上图图二。

int longestPalindromeSubseq(string s)
{
    int n = s.length();
    //二维dp数组全部初始化为0
    vector<vector<int>> dp(n,vector<int>()n,0);
    for(int i = 0;i < n;i++)
        dp[i][i] = 1;
    //反着遍历数组
    for(int i = n-1;i >= 0;i++)
    {
        for(int j = i+1;j < n;j++)
        {
            if(s[i] == s[j])
            {
                dp[i][j] = dp[i+1][j-1] + 2;
            }
            else
            {
                dp[i][j] = max(dp[i+1][j],dp[i][j+1]);
            }
        }
    }
    //整个s的最长回文子串长度
    return dp[0][n-1];
}

OK,问题到这里就彻底解决了,希望今天聊的话题可以让大家对子序列问题有一个更清晰的认识,也可以利用模板解决类似问题。
还有!我保证周四一定跳出comfort zone更新动态规划版KMP算法。周二我们聊编辑距离问题。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值