Longest Common Substring

  • Brute Force

    遍历ab所有位置的组合,向后延伸,直到遇到两个不同的字符,复杂度是\(n^3\)级别。
  1. class Solution {
  2. public:
  3. // 返回所有结果
  4. vector<string> longestCommonSubstring(string& a, string& b) {
  5. vector<string> ans;
  6. int maxLen = 0;
  7. for (int i = 0; i < a.size(); ++i) {
  8. for (int j = 0; j < b.size(); ++j) {
  9. string cur;
  10. for (int m = i, n = j; m < a.size() && n < b.size(); ++m, ++n) {
  11. if (a[m] != b[n]) {
  12. break;
  13. }
  14. cur += a[m];
  15. }
  16. if (cur.size() && cur.size() >= maxLen) {
  17. maxLen = cur.size();
  18. ans.push_back(cur);
  19. }
  20. }
  21. }
  22. return ans;
  23. }
  24. };
  • DP

    暴力解有很多重复计算:比如以\(i\)和\(j\)为起点去向后延伸,我们可能需要比较\(i+1\)和\(j+1\)、\(i+2\)和\(j+2\)...而以\(i+1\)和\(j+1\)为起点时,仍然要比较\(i+2\)和\(j+2\),重叠子问题给动态规划带来了可能。

    暴力做法是将每个\(i\)和\(j\)作为起点,现在我们考虑将\(i\)和\(j\)作为终点,令\(L(i,j)\)表示以\(i\)和\(j\)作为结尾的相同子串的最大长度,很快就可以推出:

\[L(i,j)=
\begin{cases}
1+L(i-1,j-1)& \text{a[i]=b[j]}\\
0& \text{a[i]!=b[j]}
\end{cases}\]

为了简便,假设下标从1开始,那么边界条件:\(L(0,j)=0,L(i,0)=0\)。

  1. class Solution {
  2. public:
  3. int longestCommonSubstring(string& a, string& b) {
  4. vector<vector<int>> L(1 + a.size(), vector<int>(1 + b.size(), 0));
  5. int maxLen = 0;
  6. for (int i = 1; i <= a.size(); ++i) {
  7. for (int j = 1; j <= b.size(); ++j) {
  8. if (a[i - 1] == b[j - 1]) {
  9. L[i][j] = 1 + L[i - 1][j - 1];
  10. }
  11. else {
  12. L[i][j] = 0;
  13. }
  14. maxLen = max(maxLen, L[i][j]);
  15. }
  16. }
  17. return maxLen;
  18. }
  19. };

显然时间降为\(O(n^2)\),空间升为\(O(n^2)\)。仔细观察,计算\(L(i,j)\)只需要左上方\(L(i-1,j-1)\)的信息,所以我们按照斜线方向计算,可以将空间优化到\(O(1)\):

  1. class Solution {
  2. public:
  3. int longestCommonSubstring(string& a, string& b) {
  4. // from the up-right corner
  5. int row = 0, col = b.size() - 1;
  6. int maxLen = 0;
  7. while (row < a.size()) {
  8. int curLen = 0;
  9. for (int i = row, j = col; i < a.size() && j < b.size(); ++i, ++j) {
  10. if (a[i] == b[j]) {
  11. ++curLen;
  12. }
  13. else {
  14. curLen = 0;
  15. }
  16. maxLen = max(maxLen, curLen);
  17. }
  18. if (col > 0) {
  19. --col; // 斜线左移
  20. }
  21. else {
  22. ++row; // 斜线下移
  23. }
  24. }
  25. return maxLen;
  26. }
  27. };

(TODO)输出所有的最长公共子串

Longest Common Subsequence

  • Brute Force

    找到a的所有子序列,判断是否是b的子序列,指数级复杂度,也就没有写出来的必要了。
  • DP

    重叠子问题很明显,而且LCS具有最优子结构,令\(L(i,j)\)表示以\(i\)和\(j\)作为结尾的LCS长度:

\[L(i,j)=
\begin{cases}
1+L(i-1,j-1)& \text{a[i]=b[j]}\\
max\{L(i-1,j),L(i,j-1)\}& \text{a[i]!=b[j]}
\end{cases}\]

为了简便,假设下标从1开始,那么边界条件:\(L(0,j)=0,L(i,0)=0\)。

  1. class Solution {
  2. public:
  3. int longestCommonSubsequence(string text1, string text2) {
  4. const int m = text1.size(), n = text2.size();
  5. vector<vector<int>> L(m + 1, vector<int>(n + 1, 0));
  6. int ans = 0;
  7. for(int i = 1;i <= m;++i) {
  8. for(int j = 1;j <= n;++j) {
  9. if(text1[i - 1] == text2[j - 1]) {
  10. L[i][j] = 1 + L[i - 1][j - 1];
  11. }
  12. else {
  13. L[i][j] = max(L[i - 1][j], L[i][j - 1]);
  14. }
  15. ans = max(ans, L[i][j]);
  16. }
  17. }
  18. return ans;
  19. }
  20. };

时间和空间都是\(O(mn)\)。类似的,\(L(i,j)\)依赖于左上角\(L(i-1,j-1)\)、左边\(L(i,j-1)\)、上边\(L(i-1,j)\),可以只存储上一行和当前行的\(L\)。进一步考虑:可以只存储当前行的\(L\),外加一个变量\(pre\)存储左上角\(L(i-1,j-1)\),空间可以优化到\(O(min(m,n))\):

  1. class Solution {
  2. public:
  3. int longestCommonSubsequence(string text1, string text2) {
  4. const int m = text1.size(), n = text2.size();
  5. if(m < n) {
  6. return longestCommonSubsequence(text2, text1);
  7. }
  8. vector<int> curRow(n + 1, 0);
  9. int pre = 0, ans = 0;
  10. for(int i = 1;i <= m;++i) {
  11. pre = curRow[0];
  12. for(int j = 1;j <= n;++j) {
  13. int tmp = curRow[j];
  14. if(text1[i - 1] == text2[j - 1]) {
  15. curRow[j] = 1 + pre;
  16. }
  17. else {
  18. curRow[j] = max(curRow[j], curRow[j - 1]);
  19. }
  20. ans = max(ans, curRow[j]);
  21. pre = tmp;
  22. }
  23. }
  24. return ans;
  25. }
  26. };

(TODO)输出所有的最长公共子串

Longest Increasing Subsequence

  • DP

    \(dp[i]\)表示只考虑\(i\)及之前的信息,所形成的LIS的长度:

\[dp[i]=
\begin{cases}
1+max\{dp[j]\}, 0 \leq j<i& \text{a[i]>a[j]}\\
max\{dp[j])\}, 0 \leq j<i& \text{a[i]$\leq$a[j]}
\end{cases}\]

最终答案即是\(dp\)数组的最大值:

  1. class Solution {
  2. public:
  3. int lengthOfLIS(vector<int>& nums) {
  4. if(!nums.size()) {
  5. return 0;
  6. }
  7. vector<int> dp(nums.size(), 1);
  8. int ans = 1;
  9. for(int i = 1;i < nums.size();++i) {
  10. int curMax = 0;
  11. for(int j = 0;j < i;++j) {
  12. if(nums[i] > nums[j]) {
  13. curMax = max(curMax, dp[j]);
  14. }
  15. }
  16. dp[i] = 1 + curMax;
  17. ans = max(ans, dp[i]);
  18. }
  19. return ans;
  20. }
  21. };

时间\(O(n^2)\),空间\(O(n)\)。

  • DP+Binary Search

    遍历数组的过程中,不停填充\(dp\)数组,维护\(dp\)数组使得其存储递增序列:

    如果\(nums[i]<dp[0]\),更新\(dp[0]\);

    如果\(nums[i]>dp[len]\),\(++len\),并且存入\(nums[i]\);

    如果\(nums[i]\)介于\(dp\)中间,就二分查找\(nums[i]\)的位置,并更新相应的\(dp\)值。

    举例来说,\(nums=[0,8,4,12,2]\),\(dp\)数组:

    \([0]\)

    \([0,8]\)

    \([0,4]\)

    \([0,4,12]\)

    \([0,2,12]\)

    虽然\(dp\)数组最终存储的不是LIS,但长度确是LIS的长度:
  1. class Solution {
  2. public:
  3. int lengthOfLIS(vector<int>& nums) {
  4. const int n = nums.size();
  5. if(0 == n)
  6. return 0;
  7. vector<int> dp(n);
  8. dp[0] = nums[0];
  9. int len = 0;
  10. for(int i = 1;i < n;++i) {
  11. if(nums[i] < dp[0]) {
  12. dp[0] = nums[i];
  13. }
  14. else if(nums[i] > dp[len]) {
  15. dp[++len] = nums[i];
  16. }
  17. else {
  18. int index = biSearch(dp, 0, len, nums[i]);
  19. dp[index] = nums[i];
  20. }
  21. }
  22. return len + 1;
  23. }
  24. private:
  25. int biSearch(vector<int>& nums, int l, int r, int target) {
  26. while(l < r) {
  27. int m = l + (r - l) / 2;
  28. if(nums[m] == target) {
  29. return m;
  30. }
  31. if(target > nums[m]) {
  32. l = m + 1;
  33. }
  34. else {
  35. r = m;
  36. }
  37. }
  38. return l;
  39. }
  40. };

时间\(O(nlgn)\),空间\(O(n)\)。

Longest Palindromic Substring

  • Brute Force

    枚举每个子串的起始和结束位置,判断是否回文。时间\(O(n^3)\),空间\(O(1)\)。
  • DP

    假设输入ababa,如果我们已经判断了bab是回文的,那么ababa就不需要再扫描一遍,因为两端都是a

    所以一个很直观的动规:

    令\(dp(i,j)\)去记忆\(i\)和\(j\)之间的串是否回文,那么转移方程:

\[dp(i,j)=dp(i+1,j-1)\&\&s[i]=s[j]
\]

边界条件\(dp(i,i)=true,dp(i,i+1)=(s[i]=s[i+1])\):

  1. // 填充方向:由边界条件dp(i,i)向其他地方扩展,只需要填充j>i的三角形部分
  2. class Solution {
  3. public:
  4. string longestPalindrome(string s) {
  5. vector<vector<bool>> dp(s.length() + 1, vector<bool>(s.length() + 1, false));
  6. int start = 0, end = 0;
  7. int maxLen = 1;
  8. for (int i = 0; i < s.length(); ++i) {
  9. dp[i][i] = true;
  10. if (i < s.length() - 1) {
  11. if (s[i] == s[i + 1]) {
  12. dp[i][i + 1] = true;
  13. start = i;
  14. end = i + 1;
  15. }
  16. else {
  17. dp[i][i + 1] = false;
  18. }
  19. }
  20. }
  21. for (int i = s.length() - 1; i >= 0; --i) {
  22. for (int j = i + 2; j < s.length(); ++j) {
  23. if (s[i] == s[j]) {
  24. dp[i][j] = dp[i + 1][j - 1];
  25. if (dp[i][j] && maxLen < j - i + 1) {
  26. start = i;
  27. end = j;
  28. maxLen = end - start + 1;
  29. }
  30. }
  31. else {
  32. dp[i][j] = false;
  33. }
  34. }
  35. }
  36. return s.substr(start, end - start + 1);
  37. }
  38. };

时间\(O(n^2)\),空间\(O(n^2)\)。

  • Expand Around Center

    回文串都是镜像对称的,可以遍历整个串,从当前位置向两边延伸,直到遇到不相等的字母。这里要考虑字符串长度的奇偶:
  1. class Solution {
  2. public:
  3. string longestPalindrome(string s) {
  4. const int n = s.length();
  5. if (!n)
  6. return "";
  7. int start = 0, end = 0;
  8. for (int i = 0; i < n; ++i) {
  9. int len1 = expandCenter(s, i, i);
  10. int len2 = expandCenter(s, i, i + 1);
  11. int len = max(len1, len2);
  12. if (len > end - start + 1) {
  13. start = i - (len - 1) / 2;
  14. end = i + len / 2;
  15. }
  16. }
  17. return s.substr(start, end - start + 1);
  18. }
  19. private:
  20. int expandCenter(string s, int start, int end) {
  21. while (start >= 0 && end < s.length() && s[start] == s[end]) {
  22. --start;
  23. ++end;
  24. }
  25. return end - start - 1;
  26. }
  27. };

时间\(O(n^2)\),空间\(O(1)\)。

  • Manacher's Algorithm(待填)

    时间\(O(n)\)。

Longest Palindromic Subsequence

  • DP

    令\(dp(i,j)\)表示介于\(i\)和\(j\)间的LPS的长度,那么状态转移方程:

\[dp(i,j)=
\begin{cases}
2+dp(i+1,j-1)& \text{s[i]=s[j]}\\
max\{dp(i+1,j),dp(i,j-1)\}& \text{s[i]$\neq$s[j]}
\end{cases}\]

  1. class Solution {
  2. public:
  3. int longestPalindromeSubseq(string s) {
  4. const int n = s.size();
  5. if(!n) {
  6. return 0;
  7. }
  8. vector<vector<int>> dp(n, vector<int>(n, 0));
  9. int ans = 1;
  10. for(int i = n - 1;i >= 0;--i) {
  11. for(int j = i;j < n;++j) {
  12. if(i == j) {
  13. dp[i][j] = 1;
  14. continue;
  15. }
  16. if(s[i] == s[j]) {
  17. dp[i][j] = dp[i + 1][j - 1] + 2;
  18. }
  19. else {
  20. dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
  21. }
  22. ans = max(ans, dp[i][j]);
  23. }
  24. }
  25. return ans;
  26. }
  27. };

时间\(O(n^2)\),空间\(O(n^2)\)。

同样,空间可以优化到\(O(n)\)。

  1. class Solution {
  2. public:
  3. int longestPalindromeSubseq(string s) {
  4. const int n = s.size();
  5. if(!n) {
  6. return 0;
  7. }
  8. vector<int> dp(n, 0);
  9. int ans = 1, pre = 0;
  10. for(int i = n - 1;i >= 0;--i) {
  11. pre = dp[0];
  12. for(int j = i;j < n;++j) {
  13. int tmp = dp[j];
  14. if(i == j) {
  15. dp[j] = 1;
  16. continue;
  17. }
  18. if(s[i] == s[j]) {
  19. dp[j] = pre + 2;
  20. }
  21. else {
  22. dp[j] = max(dp[j - 1], dp[j]);
  23. }
  24. pre = tmp;
  25. ans = max(ans, dp[j]);
  26. }
  27. }
  28. return ans;
  29. }
  30. };

Longest XXX的更多相关文章

  1. Linux 日志报错 xxx blocked for more than 120 seconds

    监控作业发现一台服务器(Red Hat Enterprise Linux Server release 5.7)从凌晨1:32开始,有一小段时间无法响应,数据库也连接不上,后面又正常了.早上检查了监听 ...

  2. 记一个mvn奇怪错误: Archive for required library: 'D:/mvn/repos/junit/junit/3.8.1/junit-3.8.1.jar' in project 'xxx' cannot be read or is not a valid ZIP file

    我的maven 项目有一个红色感叹号, 而且Problems 存在 errors : Description Resource Path Location Type Archive for requi ...

  3. LeetCode[3] Longest Substring Without Repeating Characters

    题目描述 Given a string, find the length of the longest substring without repeating characters. For exam ...

  4. 最长回文子串-LeetCode 5 Longest Palindromic Substring

    题目描述 Given a string S, find the longest palindromic substring in S. You may assume that the maximum ...

  5. [LeetCode] Longest Substring with At Least K Repeating Characters 至少有K个重复字符的最长子字符串

    Find the length of the longest substring T of a given string (consists of lowercase letters only) su ...

  6. eclipse 突然 一直在loading descriptor for XXX (XXX为工程名)Cancel Requested

    问题: eclipse 启动后,啥也不干,就一直在loading descriptor for XXX (XXX为工程名),,其他什么操作都不能操作. 如下图所示,保存文件也无法保存.  这个怎么办? ...

  7. leetcode--5. Longest Palindromic Substring

    题目来自 https://leetcode.com/problems/longest-palindromic-substring/ 题目:Given a string S, find the long ...

  8. 现有语言不支持XXX方法

    史上最强大的IDE也会有bug的时候哈,今天遇到这个问题特别郁闷,百度了下,果然也有人遇到过这个问题 解决方法: 1.调用的时候参数和接口声明的参数不一致(检查修改) 2.继承接口中残留一个废弃的方法 ...

  9. An App ID with Identifier 'com.XXX.XXX’ is not available. Please enter a different string.报错

    昨天刚刚升的Xcode7.3和iOS9.3,然后没怎么使用这两样就下班了,但是今天早上来了之后,就发现突然之间不能真机测试和运行代码了,一看才发现xcode报错: An App ID with Ide ...

随机推荐

  1. Python 1基础语法一(注释、行与缩进、多行语句、空行和代码组)

    一.注释Python中单行注释以 # 开头,实例如下: # 第一个注释 print ("Hello, Python!") # 第二个注释 输出结果为: ============== ...

  2. Maybatis的一些总结(二:基本使用过程)

    理清一下使用需要做的步骤 建项目,导入mybatis(3.5.2)和mysql(5.1.47)进pom.xml pom.xml需配置build时过滤器,否则会出现xml文件导出不了的问题 resour ...

  3. 再探CI,Github调戏Action手记——自动构建并发布到另一仓库

    前言 接上文初探CI,Github调戏Action手记--自动构建并发布 在学习了Action的基本操作之后 接着我们来探索Action其他可能的功能 众所周知 只有用得到的技术学习的才会最快 我也是 ...

  4. Java包机制和Javadoc的使用

    1.什么是包机制? 包(package)其实本质上就是一个文件夹,使用包是为了让相同类名的两个类可以使用,也就是操作系统中的文件夹,用来解决重名并且让相同的功能类放在同一个包,使开发更加有条理. 注意 ...

  5. C语言二维数组超细讲解

    用一维数组处理二维表格,实际是可行的,但是会很复杂,特别是遇到二维表格的输入.处理和输出. 在你绞尽脑汁的时候,二维数组(一维数组的大哥)像电视剧里救美的英雄一样显现在你的面前,初识数组的朋友们还等什 ...

  6. Element里el-badge在el-tab里视图不被渲染问题

    我们发现:el-badge绑定的变量是有数据的,但是界面上就是不渲染. 这个时候执行getTodo发现数据已经打印出来,当是视图未发送变化.于是查阅资料:vm.$forceUpdate()示例:迫使 ...

  7. 12. 前后端联调 + ( proxy代理 ) + ( axios拦截器 ) + ( css Modules模块化方案 ) + ( css-loader ) + ( 非路由组件如何使用history ) + ( bodyParser,cookieParser中间件 ) + ( utility MD5加密库 ) + ( nodemon自动重启node ) + +

    (1) proxy 前端的端口在:localhost:3000后端的端口在:localhost:1234所以要在webpack中配置proxy选项 (proxy是代理的意思) 在package.jso ...

  8. vue路由中 Navigating to current location ("/router") is not allowed

    报错原因:多次点击同一路由,导致路由被多次添加 解决方法: router/index中添加以下代码: //router/index.js Vue.use(VueRouter) //导入vue路由 co ...

  9. 看完肯定懂的 Java 字符串常量池指南

    字符串问题可谓是 Java 中经久不衰的问题,尤其是字符串常量池经常作为面试题出现.可即便是看似简单而又经常被提起的问题,还是有好多同学一知半解,看上去懂了,仔细分析起来却又发现不太明白. 背景说明 ...

  10. 链表数据-PHP的实现

    首先,链表数据的结构是: 其次,链表数据的结构特点: 随后,填充链表结构: 链表结构的数据,从链表尾部塞入数据. 最后,删除链表结构: