【算法练习Day48】回文子串&&最长回文子序列

​📝个人主页:@Sherry的成长之路

🏠学习社区:Sherry的成长之路(个人社区)

📖专栏链接:练题

🎯长路漫漫浩浩,万事皆有期待

文章目录

回文子串

647. 回文子串 - 力扣(LeetCode)

给一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。

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

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

  1. 确定dp数组(dp table)以及下标的含义
    dpi 和 dpi-1 ,dpi + 1 看上去都没啥关系。

所以我们要看回文串的性质。

我们在判断字符串S是否是回文,那么如果我们知道 s1,s2,s3 这个子串是回文的,那么只需要比较 s0和s4这两个元素是否相同,如果相同的话,这个字符串s 就是回文串。

那么此时我们是不是能找到一种递归关系,也就是判断一个子字符串(字符串的下表范围i,j)是否回文,依赖于,子字符串(下表范围i + 1, j - 1)) 是否是回文。

所以为了明确这种递归关系,我们的dp数组是要定义成一位二维dp数组。

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

  1. 确定递推公式
    在确定递推公式时,就要分析如下几种情况。

整体上是两种,就是si与sj相等,si与sj不相等这两种。

当si与sj不相等,那没啥好说的了,dpij一定是false。

当si与sj相等时,这就复杂一些了,有如下三种情况

情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串

情况二:下标i 与 j相差为1,例如aa,也是回文子串

情况三:下标:i 与 j相差大于1的时候,例如cabac,此时si与sj已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dpi + 1j - 1是否为true。

  1. dp数组如何初始化

    dpij初始化为false。

  2. 确定遍历顺序

    遍历顺序可有有点讲究了。

首先从递推公式中可以看出,情况三是根据dpi + 1j - 1是否为true,在对dpij进行赋值true的。

dpi + 1j - 1 在 dpij的左下角

如果矩阵是从上到下,从左到右遍历,那么会用到没有计算过的dpi + 1j - 1,也就是根据不确定是不是回文的区间i+1,j-1,来判断了i,j是不是回文,那结果一定是不对的。

所以一定要从下到上,从左到右遍历,这样保证dpi + 1j - 1都是经过计算的。

有的代码实现是优先遍历列,然后遍历行,其实也是一个道理,都是为了保证dpi + 1j - 1都是经过计算的。

  1. 举例推导dp数组
    举例,输入:"aaa",dpij状态如下:

图中有6个true,所以就是有6个回文子串。

cpp 复制代码
class Solution {
    public int countSubstrings(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        for (boolean[] a: dp) {
            Arrays.fill(a,false);
        }
        int result = 0;
        for (int i = s.length()-1; i>=0 ; i--) {
            for (int j = i; j <s.length() ; j++) {
                if (s.charAt(i)==(s.charAt(j))) {
                    if (j-i<=1){
                        dp[i][j] = true;
                        result++;
                    }else if(dp[i+1][j-1]==true){
                        dp[i][j] = true;
                        result++;
                    }
                }
            }
        }
        return result;
    }
}

最长回文子序列

516. 最长回文子序列 - 力扣(LeetCode)

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

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

动规五部曲分析如下:

  1. 确定dp数组(dp table)以及下标的含义

    dpij:字符串s在i, j范围内最长的回文子序列的长度为dpij。

  2. 确定递推公式

    在判断回文子串的题目中,关键逻辑就是看si与sj是否相同。

如果si与sj相同,那么dpij = dpi + 1j - 1 + 2;

如图:

如果si与sj不相同,说明si和sj的同时加入 并不能增加i,j区间回文子序列的长度,那么分别加入si、sj看看哪一个可以组成最长的回文子序列。

加入sj的回文子序列长度为dpi + 1j。

加入si的回文子序列长度为dpij - 1。

那么dpij一定是取最大的,即:dpij = max(dpi + 1j, dpij - 1);

  1. dp数组如何初始化
    首先要考虑当i 和j 相同的情况,从递推公式:dpij = dpi + 1j - 1 + 2; 可以看出 递推公式是计算不到 i 和j相同时候的情况。

所以需要手动初始化一下,当i与j相同,那么dpij一定是等于1的,即:一个字符的回文子序列长度就是1。

其他情况dpij初始为0就行,这样递推公式:dpij = max(dpi + 1j, dpij - 1); 中dpij才不会被初始值覆盖。

  1. 确定遍历顺序
    从递归公式中,可以看出,dpij 依赖于 dpi + 1j - 1 ,dpi + 1j和 dpij - 1

所以遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的。

j的话,可以正常从左向右遍历。

  1. 举例推导dp数组

输入s:"cbbd" 为例,dp数组状态如图:

红色框即:dp0s.size() - 1; 为最终结果。

cpp 复制代码
class Solution {
    public int longestPalindromeSubseq(String s) {
        int[][] dp = new int[s.length()][s.length()];
        for (int i = 0; i <s.length(); i++) {
            dp[i][i] = 1;
        }
        for (int i = s.length()-1; i >=0 ; i--) {
            for (int j =i+1; j <s.length(); j++) {
                if (s.charAt(i)==s.charAt(j)) dp[i][j]=dp[i+1][j-1]+2;
                else dp[i][j]=Math.max(dp[i][j-1],dp[i+1][j]);
            }
        }
        return dp[0][s.length()-1];
    }
}

总结:

今天我们完成了回文子串、最长回文子序列两道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

相关推荐
旺仔仔仔19 分钟前
机器人项目实战(5):从单次抓取,到接收指令连续做事
算法
旺仔仔仔27 分钟前
机器人项目实战(2):识别物体、提取点云,再到抓取姿态
算法
姜鱼问生29 分钟前
密码明明一样,为什么 GPG 说“错的“?
后端·算法·架构
启观川43 分钟前
数据结构与算法-第 3 章 常用算法-贪心算法
数据结构·笔记·python·算法·贪心算法
对空六课1 小时前
指标口径生效时间的版本查询实现
大数据·服务器·前端·算法·数据分析
对空六课1 小时前
RFM分层边界变化的回溯计算
大数据·服务器·前端·数据库·人工智能·算法·数据分析
一勺思维1 小时前
我为什么把 Agent 的核心逻辑从 LangChain 里搬出来手写
算法
碧海银沙音频科技研究院1 小时前
基于炬芯ATS308X的LE aduio LC3编解码算法功能实现
算法
小蒋观天下1 小时前
厂区人员徘徊游荡判别——基于时序轨迹速度的逗留行为分级算法落地
大数据·人工智能·深度学习·算法·机器学习·ai大模型
程序员清风2 小时前
堆与优先队列:从 Top K 到任务调度
java·数据结构·算法