字符串问题特点:
1、广泛性
1.1 字符串可以看做字符类型的数组->与数组排序、查找、调整有关
1.2 很多其它类型的问题可以转换成字符串类型的问题
2、需掌握的概念
2.1 回文
2.2 子串(连续)
2.3 子序列(不连续)
2.4 前缀树(Trie树)
2.5 后缀树和后缀数组
2.6 匹配
2.7 字典序
3、需掌握的操作
3.1 与数组有关的操作:增删改查
3.2 字符的替换
3.3 字符串的旋转
字符串题目的常见类型:
1、规则判断
1.1 判断字符串是否符合整数规则
1.2 判断字符串是否符合浮点数规则
1.3 判断字符串是否符合回文字符串规则
2、数字运算
int和long类型表达整数范围有限,所以经常用字符串实现大整数。
与大整数相关的加减乘除操作,需要模拟笔算的过程。
3、与数组操作有关的类型
3.1 数组有关的调整、排序等操作需要掌握
3.2 快速排序的划分过程需要掌握和改写
4、字符计数
4.1 哈希表
4.2 固定长度的数组(C/C++中,字符的ASCII码0-255,可用256长度的数组计数)
4.3 滑动窗口问题、寻找无重复字符子串问题、计算变位词问题
5、动态规划问题
5.1 最长公共子串
5.2 最长公共子序列
5.3 最长回文子串
5.4 最长回文子序列
6、搜索类型
6.1 宽度优先搜索
6.2 深度优先搜索
7、高级算法与数据结构解决的问题
7.1 Manacher算法解决最长回文子串问题
7.2 KMP算法解决字符匹配问题
7.3 前缀树结构
7.4 后缀树和后缀数组
7.5 通常面试题中较少出现
一、拓扑结构相同子树问题
对于两棵彼此独立的二叉树A和B,请编写一个高效算法,检查A中是否存在一棵子树与B树的拓扑结构完全相同。
给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。(结构一样,值也一样)
【分析】
普通解法:二叉树遍历+匹配问题。考察t1中以每个节点为头的子树是否与t2一致。最差时间复杂度O(n*m),n,m为t1,t2节点数。
最优解:二叉树序列化+KMP算法。时间复杂度为O(n+m)。
t1->str1, t2->str2, 用KMP算法判断str1中是否包含str2(字符串的匹配问题)
没用KMP算法:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class IdenticalTree {
public:
bool chkIdentical(TreeNode* A, TreeNode* B) {
// write code here
string str1,str2;
TreeToString(A,str1);
TreeToString(B,str2);
if(str1.find(str2)!=string::npos) return true;
else return false;
}
void TreeToString(TreeNode* A, string& str){
if(A==NULL) str+='#';
else{
str+=to_string(A->val);
TreeToString(A->left,str);
TreeToString(A->right,str);
}
}
};
二、词语变形问题
对于两个字符串A和B,如果A和B中出现的字符种类相同且每种字符出现的次数相同,则A和B互为变形词,请设计一个高效算法,检查两给定串是否互为变形词。
给定两个字符串A和B及他们的长度,请返回一个bool值,代表他们是否互为变形词。
测试样例:
"abc",3,"bca",3
返回:true
【分析】使用哈希表做字符计数即可。str1->哈希表1,str2->哈希表2,比对哈希表1与哈希表2的记录是否一致。
可以用固定长度的数组替代哈希表结构。时间复杂度为O(n), 额外空间复杂度为O(n)。
class Transform {
public:
bool chkTransform(string A, int lena, string B, int lenb) {
if(lena!=lenb) return false;
map<char,int> mapa,mapb;
for(int i=0;i<=lena;i++){
mapa[A[i]]++;
mapb[B[i]]++;
}
return mapa==mapb;
}
};
三、两串旋转问题
如果对于一个字符串A,将A的前面任意一部分挪到后边去形成的字符串称为A的旋转词。比如A="12345",A的旋转词有"12345","23451","34512","45123"和"51234"。对于两个字符串A和B,请判断A和B是否互为旋转词。
给定两个字符串A和B及他们的长度lena,lenb,请返回一个bool值,代表他们是否互为旋转词。
测试样例:
"cdab",4,"abcd",4
返回:true
【分析】最优解时间复杂度为O(n)。
1、判断str1与str2是否长度相等;
2、如果长度相等,生成str1+str1的大字符串;
3、用KMP算法判断大字符串中是否含有str2,包含->true,不包含->false。
没有采用KMP算法:
class Rotation {
public:
bool chkRotation(string A, int lena, string B, int lenb) {
if(lena!=lenb) return false;
string str=A+A;
return(str.find(B)!=string::npos);
}
};
四、句子的逆序问题
对于一个字符串,请设计一个算法,只在字符串的单词间做逆序调整,也就是说,字符串由一些由空格分隔的部分组成,你需要将这些部分逆序。
给定一个原字符串A和他的长度,请返回逆序后的字符串。
测试样例:
"dog loves pig",13
返回:"pig loves dog"
【分析】
1、实现将字符串局部所有字符逆序的函数f;
2、利用f将字符串所有字符逆序;
3、找到逆序后的字符串中每一个单词的区域,利用f将每一个单词的区域逆序。
class Reverse {
public:
string reverseSentence(string A, int n) {
reverseLocal(A,0,n-1);
reverseWord(A,n);
return A;
}
void reverseLocal(string& A, int begin, int end){
int i=begin;
int j=end;
while(i<=j){
swap(A[i++],A[j--]);
}
}
void reverseWord(string& A, int n){
int lastspace=-1;
for(int i=0;i<=n;i++){
if(A[i]==' '){
reverseLocal(A,lastspace+1,i-1);
lastspace=i;
}
}
reverseLocal(A,lastspace+1,n-1);
}
};
五、字符串移位问题
对于一个字符串,请设计一个算法,将字符串的长度为len的前缀平移到字符串的最后。
给定一个字符串A和它的长度,同时给定len,请返回平移后的字符串。
测试样例:
"ABCDE",5,3
返回:"DEABC"
要求:时间复杂度为O(n), 额外空间复杂度为O(1)。
【分析】
1、将str[0..i]部分的字符逆序
2、将str[i+1..N-1]部分的字符逆序
3、将str整体的字符逆序
(与字符交换相关的题目,大部分都是活用局部逆序函数的组合)
class Translation {
public:
string stringTranslation(string A, int n, int len) {
reverseLocal(A,0,len-1);
reverseLocal(A,len,n-1);
reverseLocal(A,0,n-1);
return A;
}
void reverseLocal(string& A, int begin, int end){
int i=begin;
int j=end;
while(i<=j){
swap(A[i++],A[j--]);
}
}
};
六、拼接最小字典序问题
对于一个给定的字符串数组,请找到一种拼接顺序,使所有小字符串拼接成的大字符串是所有可能的拼接中字典序最小的。
给定一个字符串数组strs,同时给定它的大小,请返回拼接成的串。
测试样例:
["abc","de"],2
"abcde"
【分析】最优解的时间复杂度O(nlgn),其实质是一种排序的实现。比较各字符串的字典顺序是错误的(如['ba','b'])。正确方案:如果str1+str2<str2+str1, 则str1放在前面,否则str2放在前面。
class Prior {
public:
string findSmallest(vector<string> strs, int n) {
sort(strs.begin(),strs.end(),cmp);
string str;
for(int i=0;i<=n-1;i++){
str+=strs[i];
}
return str;
}
static bool cmp(const string& str1, const string& str2){
return str1+str2 < str2+str1;
}
};
七、空格替换问题
请编写一个方法,将字符串中的空格全部替换为“%20”。假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由大小写的英文字母组成。
给定一个string iniString 为原始的串,以及串的长度 int len, 返回替换后的string。
测试样例:
"Mr John Smith”,13
返回:"Mr%20John%20Smith"
”Hello World”,12
返回:”Hello%20%20World”
【分析】遍历str,发现空格数量,计算替换空格后str的长度,由右至左拷贝。
class Replacement {
public:
string replaceSpace(string iniString, int length) {
int cnt=0;
for(int i=0;i<=length-1;i++){
if(iniString[i]==' ') cnt++;
}
int new_len = length+2*cnt;
iniString = iniString.append(2*cnt,'0');
int i=new_len-1;
int j=length-1;
while(j>=0){
if(iniString[j]!=' ') iniString[i--]=iniString[j--];
else{
iniString[i]='0';
iniString[i-1]='2';
iniString[i-2]='%';
i -= 3;
j--;
}
}
return iniString;
}
};
八、合法括号序列判断问题
对于一个字符串,请设计一个算法,判断其是否为一个合法的括号串。
给定一个字符串A和它的长度n,请返回一个bool值代表它是否为一个合法的括号串。
测试样例:
"(()())",6
返回:true
测试样例:
"()a()()",7
返回:false
测试样例:
"()(()()",7
返回:false
【分析】最优解时间复杂度为O(n), 额外空间复杂度为O(1)。
1、整型变量num,代表'('出现次数与')'出现次数的差值;
2、遍历过程中如果遇到'('则num++;
3、遍历过程中如果遇到')'则num--;
4、遍历过程中如果num<0,则直接返回false;
5、如果一直没有出现情况4,则一直遍历下去;
6、遍历完成后,如果num==0,则返回true,否则返回false。
class Parenthesis {
public:
bool chkParenthesis(string A, int n) {
if(n%2==1) return false;
int cnt=0;
for(int i=0;i<=n-1;i++){
if(A[i] != '(' && A[i] != ')') return false;
else if(A[i]=='(') cnt++;
else cnt--;
if(cnt<0) return false;
}
return cnt==0;
}
};
九、最长无重复字符子串问题
对于一个字符串,请设计一个高效算法,找到字符串的最长无重复字符的子串长度。
给定一个字符串A及它的长度n,请返回它的最长无重复字符子串长度。保证A中字符全部为小写英文字符,且长度小于等于500。
测试样例:
"aabcb",5
返回:3
【分析】最优解时间复杂度为O(n), 额外空间复杂度为O(n)。
求出以str中每个字符结尾的情况下,最长无重复字符子串的长度,并在其中找出最大值返回。
求出i位置结尾的最长无重复子串的长度,需要两个变量辅助计算,一是哈希表map,记录了每种字符之前出现的位置,二是整型变量pre,即以i-1位置结尾的最长无重复子串的长度。
map.get('当前字符')->当前字符上次出现的位置,该位置的下一位置记为位置A,以i-1结尾的最长无重复字符子串的最左位置记为位置B,位置A和位置B中较右的位置到当前位置,即为以当前位置结尾的最长无重复字符子串。更新map、pre。
class DistinctSubstring {
public:
int longestSubstring(string A, int n) {
if(n<=1) return n;
int ans=0;
int left=0;
map<char,int> m;
for(int i=0;i<=n-1;i++){
m[A[i]]=-1;
}
for(int i=0;i<=n-1;i++){
left=max(left,m[A[i]]+1);
ans=max(ans,i-left+1);
m[A[i]]=i;
}
return ans;
}
};