day57 | 647. 回文子串、516.最长回文子序列

目录:

解题及思路学习

647. 回文子串

https://leetcode.cn/problems/palindromic-substrings/

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

思考:要连续的子串,并统计回文的个数。之前学过的回溯算法应该能解。

如果按照动态规划的思路来。

随想录:dp数组的定义,如果一个区间是回文子串的话,那往两边各取一个数,如果相同,那就是一个新的回文子串。

1、含义

根据回文的定义,布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4i19JB5v-1689737666160)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e71dd2de-1725-4c72-b208-446d6e89b69e/Untitled.png)]

2、递推公式

整体上可以分为s[i] 和 s[j] 是否相等两种情况。

当s[i] 和 s[j] 相等时:

① 下标相同,例如一个字符,自然是回文子串。

② 下标相差1,例如aa,也是回文子串。

③ 下标相差大于1,例如cabac,此时s[i]与s[j] 相同,区间 [i + 1, j - 1] 也是回文子串,所以是。

当s[i] 和s[j] 不相等的时候,自然就不是回文子串。

3、初始化

dp[i][j] 全部初始化为false

4、遍历顺序

因为dp[i][j] 依赖左下角的的结果,所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-81KrNtKc-1689737666162)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/21b0a1bb-d85c-43f0-9ef2-8dace0fb2900/Untitled.png)]

5、举例推导dp数组

举例,输入:“aaa”,dp[i][j]状态如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RFeCAPuO-1689737666164)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e2fd9506-2085-4ebe-b125-45879a9a530c/Untitled.png)]

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--) {  // 注意遍历顺序
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (j - i <= 1) { // 情况一 和 情况二
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // 情况三
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n^2)

双指针法

首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。

在遍历中心点的时候,要注意中心点有两种情况

一个元素可以作为中心点,两个元素也可以作为中心点。

那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到。

所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。

class Solution {
public:
    int countSubstrings(string s) {
        int result = 0;
        for (int i = 0; i < s.size(); i++) {
            result += extend(s, i, i, s.size()); // 以i为中心
            result += extend(s, i, i + 1, s.size()); // 以i和i+1为中心
        }
        return result;
    }
    int extend(const string& s, int i, int j, int n) {
        int res = 0;
        while (i >= 0 && j < n && s[i] == s[j]) {
            i--;
            j++;
            res++;
        }
        return res;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

双指针解法也挺巧妙的。

516.最长回文子序列

https://leetcode.cn/problems/longest-palindromic-subsequence/

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

思考:可以仿照上题的思路,对于[i, j] 区间内的最长子序列表示为dp[i][j],如果s[i] == s[j] ,那么最长长度就等于[i+1, j-1] + 2, 否则,只考虑一端的最大值。

1、含义

dp[i][j] 表示区间[i,j] 内s的最长的回文子序列长度。

2、递推公式

当s[i] 和s[j] 相等时:

① i和j相等,那么dp[i][j] = 1;

② i和j相差1,那么dp[i][j] = 2;

③ i和j相差大于1, 那么dp[i][j] = dp[i+1][j - 1] + 2;

当s[i] 和s[j] 不相等时:

dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 即,不带边界的最大长度。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (i == j) dp[i][j] = 1;
                    else if (j - i  == 1) dp[i][j] = 2;
                    else 
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][s.size() - 1];
    }
};
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n^2)

随想录:基本上思想一样的,只是在代码实现上略有差别。对于i和j相等的情况,分别单独赋值为1。然后在遍历j的时候从 i+1开始遍历。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i + 1; j < s.size(); 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]);
                }
            }
        }
        return dp[0][s.size() - 1];
    }
};
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n^2)

动态规划总结篇

https://programmercarl.com/动态规划总结篇.html

知识点记录

知识点

回文子串

个人反思

动态规划类题目,最主要的是递推公式。定义的好可以省事儿很多,其他细节也很重要。

动规五部曲分别为:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

按照这个来分析挺好的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值