字符串
遇到的问题无非就这么几类:字符串翻转/公共子序列/回文子串/重复子串。
首先我们发现,字符串经常和动态规划挂钩!还有一些独特的算法,如中轴法,滑动窗口(分固定和不固定)。此外,我们还要关注一些经典问题:KMP算法,马拉车算法,表达式求解等。
字符串问题经常用到unordered_map和unordered_set两种stl容器。
练手题:1143(dp出现过,高频) 225 696(题解很有趣)3(滑动窗口问题)
下面进行具体讲解
-
1143
:求给定字符串的最长公共子序列这是一个非常明显的动态规划问题。以测试数据text1=”abcde“,text2=”bde“为例,在有两个字符串/数组的问题中,我们常见的模拟策略是固定一个字符串text1,将text2的字符串从头开始依次尝试和整个text1匹配并填充dp数组,即先将”b“与text1全部匹配,填满dp[1],然后根据dp[1]与”bd“将dp[2]填充,循序渐进。总时间复杂度是O(mn)。边界情况直接当作一个字符串为空,按照常规思路去填充即可。
动态规划看起来是就某个中间状态进行分析,但是实际填充的时候也是从无到有,从最小的子问题开始逐步解决的,所以分析初始状态对求解是非常有帮助的。
int longestCommonSubsequence(string text1, string text2) { int len1=text1.length(); int len2=text2.length(); vector<vector<int> > dp(len1+1,vector<int>(len2+1,0)); for(int i=1;i<=len1;i++){ for(int j=1;j<=len2;j++){ if(text1[i-1]==text2[j-1])//注意要-1 dp[i][j]=dp[i-1][j-1]+1;//状态转移方程 else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } return dp[len1][len2]; }
-
696:给定一个 0-1 字符串,求有多少非空子字符串的 0 和 1 数量相同。
dp和动态规划都可以,先看看中心扩展法(后面有具体讲)。此外还有一种偏数学的解法。
class Solution { public: int countBinarySubstrings(string s) { int sum=0; for(int i=0;i<s.length()-1;++i){ int j=i+1; if(s[i]!=s[j]){//符合初始状态 ++sum; int k=i-1; j++;//开始扩展 while(k>=0 && j<s.length() && s[k]==s[k+1] && s[j]==s[j-1]){ ++sum; k--; j++; } } } return sum; } };
我们可以从左往右遍历数组,记录和当前位置数字相同且连续的长度,以及其之前连续的不同数字的长度。举例来说,对于 00110 的最后一位,我们记录的相同数字长度是 1,因为只有一个连续 0; 我们记录的不同数字长度是 2,因为在 0 之前有两个连续的 1。若不同数字的连续长度大于等于 当前数字的连续长度,则说明存在一个且只存在一个以当前数字结尾的满足条件的子字符串。
正确性:因为以每个字符结尾的符合要求的子字符串最多只有1个。回文子串这个方法就不能用。
int countBinarySubstrings(string s) { int pre = 0, cur = 1, count = 0; //pre:与当前位置不同的字符串长度