字符串
1.字符串比较
题目一:判断两个字符串包含的字符是否完全相同
我们可以利用哈希表或者数组统计两个数组中每个数字出现的频次,若频次相同,则说明它们包含的字符完全相同。
bool isAnagram(string s,string t)
{
if(s.length() != t.length())
{
return false;
}
vector<int>counts(26,0);
for(int i=0;i<s.length();++i)
{
++counts[s[i]-'a'];
--counts[t[i]-'a'];
}
for(int i=0;i<26;++i)
{
if(count[i])
return false;
}
return true;
}
题目二:判断两个字符串是否同构。同构的定义是,可以通过把一个字符串的某些相同的字符转换成
另一些相同的字符,使得两个字符串相同,且两种不同的字符不能够被转换成同一种字符。
可以将问题转化一下:记录两个字符串每个位置的字符第一次出现的位置,如果两个字符串中相同位置的字符与它们第一次出现的位置一样,那么这两个字符串同构。举例来说,对于“paper”和“title”,假设我们现在遍历到第三个字符“p”和“t”,发现它们第一次出现的位置都在第一个字符,则说明目前位置满足同构。
bool isIsomorphic(string s,string t)
{
vector<int>s_first_index(256,0),t_first_index(256,0);
for(int i=0;i<s.length();++i)
{
if(s_first_index[s[i]] != t_first_index[t[i]])
{
return false;
}
s_first_index[s[i]] = t_first_index[t[i]] = i + 1;
}
return true;
}
题目三:给定一个字符,求其有多少个回文子字符串。回文的定义是左右对称。
输入是一个字符串,输出一个整数,表示回文字符串的数量
我们可以从字符串的每个位置开始,向左向右延长,判断存在多少以当前位置为中轴的回文子字符串。
int countSubstrings(string s)
{
int count = 0;
for(int i=0;i<s.length();++i)
{
count += extendSubstring(s,i,i)//奇数长度
count += extendSubstring(s,i,i+1);//偶数长度
}
return count;
}
int extendSubstring(string s,int l,int r)
{
int count = 0;
while(l>=0 && r<s.length() && s[l]==s[r])
{
--l;
++r;
++count;
}
return count;
}
题目四:给定一个 0-1 字符串,求有多少非空子字符串的 0 和 1 数量相同。
从左往右遍历数组,记录和当前位置数字相同且连续的长度,以及其之前连续的不同数字的长度。举例来说,对于 00110 的最后一位,我们记录的相同数字长度是 1,因为只有一个连续 0;我们记录的不同数字长度是 2,因为在 0 之前有两个连续的 1。若不同数字的连续长度大于等于当前数字的连续长度,则说明存在一个且只存在一个以当前数字结尾的满足条件的子字符串。
int countBinarySubstring(string s)
{
int pre=0,cur=1,count=0;
for(int i=1;i<s.length();++i)
{
if(s[i] == s[i-1])
{
++cur;
}
else{
pre=cur;
cur=1;
}
if(pre >=cur)
{
++count;
}
}
return count;
}
2.字符串理解
题目一:给定一个包含加减乘除整数运算的字符串,求其运算结果,只保留整数。
如果我们在字符串左边加上一个加号,可以证明其并不改变运算结果,且字符串可以分割成多个 < 一个运算符,一个数字 > 对子的形式;这样一来我们就可以从左往右处理了。由于乘除的优先级高于加减,因此我们需要使用一个中间变量来存储高优先度的运算结果。此类型题也考察很多细节处理,如无运算符的情况,和多个空格的情况等等。
//主函数
int calculate(string s)
{
int i=0;
rerturn parseExpr(s,i);
}
//辅函数 - 递归parse从位置i开始的剩余字符串
int parseExpr(const string&s,int&i)
{
char op='+';
long left=0,right=0;
while(i<s.length())
{
if(s[i] != ' ')
{
long n=parseNum(s,i);
switch(op)
{
case '+':left += right; right=n; break;
case '-':left += right; right=-n; break;
case '*':right *=n; break;
case '/':right /=n; break;
}
if(i<s.length())
{
op=s[i];
}
}
++i;
}
return left+right;
}
//辅函数 - parse从位置i开始的一个数字
long parseNum(const string&s,int&i)
{
long n=0;
while(i<s.length() && isdigit(s[i]))
{
n=10 * n + (s[i++] - '0');
}
return n;
}
3.字符串匹配
题目一:判断一个字符串是不是另一个字符串的子字符串,并返回其位置。
使用著名的Knuth-Morris-Pratt(KMP)算法,可以在 O(m + n) 时间利用动态规划完成匹配。
//主函数
int strStr(string haystack,string needle)
{
int k=-1, n=haystack.length(), p=needle.length();
if(p==0) return 0;
vector<int>next(p,-1);//-1表示不存在相同的最大前缀和后缀
calNext(needle,next);//计算next数组
for(int i=0;i<n;++i)
{
while(k>-1 && needle[k+1] != haystack[i])
{
k=next[k];//有部分匹配,往前回溯
}
if(needle[k+1] == haystack[i])
{
++k;
}
if(k == p-1)
{
return i-p+1;//说明k移动到needle的最末端,返回相应的位置
}
}
return -1;
}
//辅函数-计算next数组
void calNext(const string &&needle,vector<int>&next)
{
for(int j=1, p=-1;j<needle.length();++i)
{
while(p>-1 && needle[p+1] != needle[j])
{
p=next[p];//如果下一位不同,往前回溯
}
if(needle[p+1] == needle[j])
{
++p;//如果下一位相同,更新相同的最大前缀和最大后缀长
}
next[j] = p;
}
}
备注:虽然很认真的在撸代码,却是在半知半解的情况下去做的;本来现在的事情应该在学校是就早早完成,深谙其算法原理及实现方法,无奈却是发生在工作一段时间的自省之后;往事不堪回首,现在努力思考,以图尽量做好当下,少发生一些让未来的我会后悔和感到光阴虚度的回忆