当前位置: 首页 > article >正文

多维动态规划-面试高频!-最长公共子序列和最长公共子串、回文串-c++实现和详解

1143. 最长公共子序列

中等

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:

  • 1 <= text1.length, text2.length <= 1000
  • text1text2 仅由小写英文字符组成。

解法

  • 定义 dp [ i ] [ j ] 表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列。 状态定义特别重要,dp[ i] [ j] 其中i和j都是开区间,所以dp[0] [0]=0只是表示两个空串的最长公共子序列,这种方式省去了所有的初始化
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        // 状态定义特别重要
        // dp[i][j] i和j都是开区间
        // 如果上一个都相等,就给下一个赋值
        // 遍历只是表现形式
        int M = text1.size();
        int N = text2.size();
        // dp的初始化
        vector<vector<int>> dp(M + 2, vector<int>(N + 2, 0));
        for (int i = 1; i <= M; i++) {
            for (int j = 1; j <= N; j++) {
                if (text1[i - 1] == text2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                    dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
        return dp[M][N];
    }
};

BM66 最长公共子串

题目链接-牛客

  • 这道题我面试碰到了,当时不会做!没想到力扣没有这道题!

描述

给定两个字符串str1和str2,输出两个字符串的最长公共子串

题目保证str1和str2的最长公共子串存在且唯一。

数据范围: 1≤∣𝑠𝑡𝑟1∣,∣𝑠𝑡𝑟2∣≤50001≤∣str1∣,∣str2∣≤5000
要求: 空间复杂度 𝑂(𝑛2)O(n2),时间复杂度 𝑂(𝑛2)O(n2)

示例1

输入:

"1AB2345CD","12345EF"

复制

返回值:

"2345"

复制

备注:

1≤∣𝑠𝑡𝑟1∣,∣𝑠𝑡𝑟2∣≤5 0001≤∣str1∣,∣str2∣≤5000

解法

  • 本题的区别是子序列和子串的定义不同,所以本题如果两个字符不同的话就应该直接把dp清零!

  • 我自己的代码如下:

    #include <vector>
    class Solution {
      public:
        /**
         * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
         *
         * longest common substring
         * @param str1 string字符串 the string
         * @param str2 string字符串 the string
         * @return string字符串
         */
        string LCS(string str1, string str2) {
            // write code here
            int M = str1.size();
            int N = str2.size();
            int maxx = 0;
            vector<vector<int>>dp(M + 2, vector<int>(N + 2, 0)); //初始化、
            string res;
            for (int i = 1; i <= M; i++) {
                for (int j = 1; j <= N; j++) {
                    if (str1[i - 1] == str2[j - 1]) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                        if (dp[i][j] > maxx) {
                            maxx = dp[i][j];
                            int l = dp[i][j];
                            res = str1.substr(i - l, l);
                        }
                    } else dp[i][j] = 0;
                }
            }
            return res;
        }
    };
    

后来看到别人更好的代码:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string LCS(const string& str1, const string& str2) {
    int maxLength = 0; // 记录最长公共子串的长度
    int maxLastIndex = 0; // 记录最长公共子串最后一个字符在字符串str1中的位置

    // 创建二维动态规划表,大小为(str1.length() + 1) x (str2.length() + 1)
    vector<vector<int>> dp(str1.length() + 1, vector<int>(str2.length() + 1, 0));

    // 遍历str1和str2的每一个字符
    for (int i = 0; i < str1.length(); i++) {
        for (int j = 0; j < str2.length(); j++) {
            // 当字符相等时,更新dp表的值
            if (str1[i] == str2[j]) {
                dp[i + 1][j + 1] = dp[i][j] + 1;

                // 如果当前公共子串的长度大于之前记录的最大值,则更新最大长度和最后一个字符的位置
                if (dp[i + 1][j + 1] > maxLength) {
                    maxLength = dp[i + 1][j + 1];
                    maxLastIndex = i;
                }
            } else {
                // 当字符不相等时,将dp表中的值设为0,表示没有公共子串
                dp[i + 1][j + 1] = 0;
            }
        }
    }

    // 使用substr函数截取最长公共子串
    return str1.substr(maxLastIndex - maxLength + 1, maxLength);
}


这里补充一个点,求回文串、回文数等等的通法:双指针中心扩散

最长回文串

  • 我先贴一份我自己写的代码,我的初始想法是,把s做一个reverse,然后求s1和s的最长公共子串,就是回文串
class Solution {
public:
    string longestPalindrome(string s) {
        // 最长回文子串=reverse的最长公共子串!
        string s1 = s;
        reverse(s.begin(), s.end());
        int M = s.size();
        vector<vector<int>> dp(M + 1, vector<int>(M + 1, 0));
        int maxlen = 0;
        int end = 0;
        for (int i = 1; i <= M; i++) {
            for (int j = 1; j <= M; j++) {
                if (s[i - 1] == s1[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    if (dp[i][j] > maxlen) {
                        maxlen = max(maxlen, dp[i][j]); // 最大值
                        end = i - 1;
                    }
                } else {
                    dp[i][j] = 0;
                }
            }
        }
        return s.substr(end - maxlen + 1, maxlen);
    }
};
  • 样例都过了,卡在了后面的一个样例,说明这个方法是错的!
    在这里插入图片描述
  • 下面附正确的代码(已经写上完整注释):
class Solution {
public:
    // 辅助函数,用于计算以 s[l] 和 s[r] 为中心的最长回文子串的长度
    int help(string s, int l, int r) {
        int len = s.size();

        // 当 l 和 r 不超出范围,并且 s[l] == s[r] 时,向外扩展
        while (l >= 0 && r < len && s[l] == s[r]) {
            l--;  // 左边指针左移
            r++;  // 右边指针右移
        }
        // 返回回文子串的长度,注意此时 l 和 r 已经多走了一步
        return r - l - 1; // 回文串的长度为 r - l - 1
    }

    // 主函数,返回最长的回文子串
    string longestPalindrome(string s) {
        int len = s.size();  // 字符串长度
        int begin = 0;  // 记录最长回文子串的起始位置
        int maxlen = 0; // 记录最长回文子串的长度

        // 遍历字符串中的每个字符,以它们作为回文的中心
        for (int i = 0; i < len; i++) {
            // 以 i 为中心扩展,找奇数长度的回文子串
            int len1 = help(s, i, i);
            // 以 i 和 i+1 为中心扩展,找偶数长度的回文子串
            int len2 = help(s, i, i + 1);

            // 更新最长回文子串的长度和起始位置
            if (len1 > maxlen || len2 > maxlen) {
                if (len1 > len2) {
                    // 如果奇数长度的回文子串更长
                    maxlen = len1;  // 更新最大长度
                    begin = i - (len1 - 1) / 2; // 计算起始位置
                } else {
                    // 如果偶数长度的回文子串更长
                    maxlen = len2;  // 更新最大长度
                    begin = i - (len2 - 2) / 2; // 计算起始位置
                }
            }
        }
        // 返回从起始位置开始的最大回文子串
        return s.substr(begin, maxlen);
    }
};


http://www.kler.cn/a/291528.html

相关文章:

  • 【踩坑记录】C编程变量未初始化导致的程序异常
  • 【C++】模板与泛型编程(一):定义模板,成员模板
  • go语言并发文件备份,自动比对自动重命名(逐行注释)
  • java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
  • 观察者模式(sigslot in C++)
  • ubuntu 如何重装你的apt【apt-get报错: symbol lookup error/undefined symbol】
  • K8s的Pv和Pvc就是为了pod数据持久化
  • AMV格式转换,试试这五种转换方式
  • Mysql从0到1
  • Arduino IDE安装
  • 【编程贴士】编写类与函数时,什么时候用const、static、inline等关键字?(C、C++)
  • 移动端设计规范:提升用户体验的核心要素
  • 基于阿里云函数计算(FC)x 云原生 API 网关构建生产级别 LLM Chat 应用方案最佳实践
  • F - Simplified Reversi 矩阵侧边视角 修改
  • Python Invoke:强大的自动化任务库
  • C++ 重载运算符和重载函数
  • 构建全景式智慧文旅生态:EasyCVR视频汇聚平台与AR/VR技术的深度融合实践
  • Spark MLlib模型训练—回归算法 Linear regression
  • 不限专业和工作经验,这个含金量巨高的IT证书,90%的大学生都不知道!
  • FPGA 编程基础, 赋值操作符, 运算符使用, 条件表达式, 信号操作方法
  • 工业应用软件开发实训室(边缘计算)建设方案
  • sportbugs报告路径在linux和windows中的配置差异
  • Linux 文件操作相关函数整理
  • 基于django的在线音乐网站设计/基于python的音乐播放系统
  • Node.js模块系统
  • C#转java工具