516. 最长回文子序列 1312. 让字符串成为回文串的最少插入次数 动态规划

88 篇文章 0 订阅
26 篇文章 0 订阅

思路整理自公众号: labuladong

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1:
输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 "bbbb"。
示例 2:
输入:
"cbbd"
输出:
2
一个可能的最长回文子序列为 "bb"。
提示:
1 <= s.length <= 1000
s 只包含小写英文字母

解题思路

注意子序列和子串的区别,子串是连续的,子序列是不连续的。
这类问题都是让你求一个最长子序列,因为最短子序列就是一个字符嘛,没啥可问的。一旦涉及到子序列和最值,那几乎可以肯定,考察的是动态规划技巧,时间复杂度一般都是 O(n^2)。
有两种思路定义dp数组。

1、第一种思路模板是一个一维的 dp 数组:

int n = array.length;
int[] dp = new int[n];

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

2、第二种思路模板是一个二维的 dp 数组:

int n = arr.length;
int[][] dp = new dp[n][n];

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

搞清楚dp数组的含义
2.1 涉及两个字符串/数组时(比如最长公共子序列),dp 数组的含义如下:
在子数组arr1[0…i]和子数组arr2[0…j]中,我们要求的子序列(最长公共子序列)长度为dp[i][j]。
2.2 只涉及一个字符串/数组时(比如本文要讲的最长回文子序列),dp 数组的含义如下:
在子数组array[i…j]中,我们要求的子序列(最长回文子序列)的长度为dp[i][j]

在本题中,定义dp数组,其含义为在子串s[i…j]中,最长回文子序列的长度为dp[i][j]在这里插入图片描述

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        # 动态规划
        n = len(s)
        # dp[i][j] 表示在子串s[i:j+1]中,最长回文子序列的长度为dp[i][j]
        dp = [[0 for _ in range(n)] for _ in range(n)]
        # 他的子问题就是dp[i+1][j-1]
        # 假设已经知道s[i+1:j]的最长子序列为dp[i+1][j-1],要求dp[i][j],
        # 情况一:当s[i]==s[j]时,那么s[i:j+1]的最长回文子序列长度就是s[i+1:j]的最长子序列加上s[i]和s[j]
        # 情况二:当s[i]!=s[j]时,说明它俩不可能同时出现在s[i:j+1]的最长回文子序列中,
        # 那么把它俩分别加入s[i+1:j-1]中,看看哪个子串产生的回文子序列更长即可: dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
        # if (s[i] == s[j])
        # 它俩一定在最长回文子序列中
        #     dp[i][j] = dp[i + 1][j - 1] + 2;
        # else
        #     s[i+1..j] 和 s[i..j-1] 谁的回文子序列更长?选择更长的子序列
        #     dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

        # 初始化dp
        for i in range(n):
            dp[i][i] = 1 
        # 必须满足 i < j 
        for i in range(n - 1, -1, -1):
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2 
                else:
                    dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])

        return dp[0][n - 1]

################################# 手动分割 ######################################

1312. 让字符串成为回文串的最少插入次数

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
示例 1:
输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。
示例 2:
输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。
示例 3:
输入:s = "leetcode"
输出:5
解释:插入 5 个字符后字符串变为 "leetcodocteel" 。
示例 4:
输入:s = "g"
输出:0
示例 5:
输入:s = "no"
输出:1
提示:
1 <= s.length <= 500
s 中所有字符都是小写字母。

解题思路

定义dp数组,dp[i][j]表示对于字符串s[i…j],(包含第j个),最少需要进行dp[i][j]次插入才可以变为回文字符串,最终的答案为dp[0][n-1]。
对于dp[i][j],dp[i+1][j-1]就是他的子问题。

class Solution(object):
    def minInsertions(self, s):
        """
        :type s: str
        :rtype: int
        """
        n = len(s)
        # dp[i][j]表示对于字符串s[i...j],(包含第j个),最少需要进行dp[i][j]次插入才可以变为回文字符串,最终的答案为dp[0][n-1]
        # 当i==j时,相当于是一个字符,本身就是回文,所以dp[i][i]==0
        dp = [[0 for _ in range(n)] for _ in range(n)]
        # 状态转移方程
        # 对于dp[i][j],dp[i+1][j-1]就是他的子问题,假设已经计算出dp[i+1][j-1],相当于s[i:j+1]已经是一个回文子串了
        # 当s[i]==s[j]时,s[i:j+1]本身就是一个回文了,不需要任何插入,所以dp[i][j]=dp[i+1][j-1]
        # 如果s[i]!=s[j],分情况讨论:
        # 如果先把s[j]插到s[i]右边,同时把s[i]插到s[j]右边,这样得到的肯定是一个回文,但不一定是最优的
        # 比如有一边是回文,如abbbb,这个时候只需要在右边添加一个a即可,
        # 所以第一步,做选择,先将s[i:j] 或s[i+1:j+1]变为回文,只需判断哪个变为回文所需的操作少就变哪个
        # 即判断dp[i][j-1] 和dp[i+1][j]的大小,哪个小就选哪个
        # 所以,根据第一步的选择,将s[i:j+1]变为回文。
        # 如果第一步中选择把s[i+1:j+1]变为回文,那么只需在s[i+1:j+1]的右边插入s[i]即可将s[i:j+1]变为回文  
        # 或者第一步中选择将s[i:j]变为回文,只需在s[i:j]的左边插入s[j]即可
        # 所以状态转移方程为
        # if s[i]==s[j]:
        #     dp[i][j] = dp[i+1][j-1]
        # else:
        #     dp[i][j] = min(dp[i][j-1], dp[i+1][j]) + 1 
        # 前面知道,dp[i][i]=0,而dp[i][j]与dp[i][j-1], dp[i+1][j]和dp[i+1][j-1]有关
        # 为了保证每次计算dp[i][j]时,这三个状态都已经被计算,我们一般选择从下向上,从左到右遍历dp数组:

        # 从下往上遍历,要先得到i+1,再有i
        for i in range(n - 1, -1, -1):
            # 从左往右遍历,要先得到j-1,再有j
            # j从i+1开始,因为j要大于等于i
            for j in range(i + 1, n):
                if s[i]==s[j]:
                    dp[i][j] = dp[i+1][j-1]
                else:
                    dp[i][j] = min(dp[i][j-1], dp[i+1][j]) + 1 
        return dp[0][n-1]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值