字符串面试题的特点:
1. 广泛性
字符串可以看做字符类型的数组,与数组排序,查找,调整有关。
很多其他类型的面试题可以看做字符串类型的面试题。
2.需要掌握的概念
回文
子串(连续)
子序列(不连续)
前缀树(Trie树)
后缀树和后缀数组
匹配
字典序
3. 需要掌握的操作
与数组有关的操作:增删改查
字符的替换
字符串的旋转
字符串题目的常见类型
1. 规则判断
判断字符串是否符合整数规则
判断字符串是否符合浮点数规则
判断字符串是否符合回文字符串规则等等
2. 数字运算
int和long类型表达整数范围有限,所以经常用字符串实现大整数;
与大整数相关的加减乘除操作,需要模拟笔算的过程。
3. 与数组操作有关的类型
数组有关的调整、排序等操作需要掌握。
快速排序的划分过程需要掌握和改写。
4. 字符计数:遍历一遍字符串,统计每种字符出现的次数。
可以用哈希表;
固定长度的数组:
c/c++(256长度)JAVA(65536长度)
因为c中ACSII码范围是0-255,JAVA中ASCII码范围是0-65535!!!
可以用固定长度的数组代替哈希表进行每个字符的次数统计!
常见问题有滑动串口问题、寻找无重复字符子串问题以及计算变位词问题!
5. 动态规划类型
最长公共子串
最长公共子序列
最长会问子串
最长回文子序列
等等
6. 搜索问题
宽度优先搜索
深度优先搜索
7. 高级算法与数据结构解决的问题
Manacher(马拉车)算法解决最长回文子串问题
KMP算法解决字符串匹配问题
前缀树结构(生成比较高级的数据结构来解决字符串问题)
后缀树和后缀数组
上面这些算法面试中比较少,因为实现起来比较复杂
案例一:给定彼此独立的两颗树,头结点分别为t1和t2,判断t1中是否有与t2树拓扑结构完全相同的子树。
这道题看起来是一个树的问题,实际上是字符串问题。
普通解法为二叉树遍历+匹配问题:如果t1节点数为n,t2节点数为m,最差情况为t1的每个节点都和t2的头节点相同,并且都没有和t2一模一样的子树,这样的话就需要比较n*m次。所以普通解法的时间复杂度为o(m*n);
最优解法为二叉树序列化+KMP算法:时间复杂度为o(m+n),具体做法如下:
二叉树t1----------序列化成为字符串str1
二叉树t2----------序列化成为字符串str2
用KMP算法判断str1中是否含有str2。如果str1包含str2,说明t1某棵子树与t2一致,否则,说明t1中没有任何一棵子树与t2一致。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
vector
class IdenticalTree
{
public:
bool
chkIdentical (TreeNode* A, TreeNode* B)
{
// write code here
str1 = treeSerialization (A); //其实是将二叉树补充成完全二叉树之后按照先序遍历的顺序保存到数组中的
str.clear ();
str2 = treeSerialization (B);
size_t i = 0;
size_t j = 0;
bool flag = false;
//先计算B的next[i]函数,next[i]函数表示的是模式串中长度为i的字符串的前缀和后缀最大公共长度,因此next{0}为0;
size_t* nextstr2;
nextstr2 = getNext (str2);
//下面利用next函数进行比较,也就是KMP算法
while (i < str1.size () && j < str2.size ())
{
if (str1[i] == str2[j])
{
flag = true;
i++;
j++;
continue;
}
else if (j > 0 && str1[i] != str2[j])
{
flag = false;
j = nextstr2[j];
}
else
{
i++;
}
}
return flag;
}
//需要注意的是前缀和后缀都是不包括整个字符串的
size_t*
getNext (vector
{
size_t len = b.size ();
size_t j = 0;
size_t *next = new size_t[len + 1]; //这里的长度之所以为len+1是因为next数组的下标表示的是字符串的长度,而字符串的长度中最大的长度是len,最小的长度是0,所以一共是len+1
next[0] = next[1] = 0;
for (size_t i = 1; i < len; i++)
{ //遍历到b[i]的时候,就需要计算长度为i+1的字符串的前缀和后缀的公共长度,也就是next[i+1]
//j在每次循环开始都表示next[i]的值,同时b[j]也表示需要比较的下一个位置
while (j > 0 && b[i] != b[j])
j = next[j];
if (b[i] == b[j])
j++;
next[i + 1] = j;
}
return next;
}
vector
treeSerialization (TreeNode* C)
{
if (C == NULL)
{
return str;
}
else
{
str.push_back (C->val);
if (C->left != NULL)
{
treeSerialization (C->left);
}
else
{
str.push_back ('#');
}
if (C->right != NULL)
{
treeSerialization (C->right);
}
else
{
str.push_back ('#');
}
}
return str;
}
protected:
vector
};
对于上面的程序来说,因为vector对象调用size()函数的时候,返回的值是一个size_t类型,所以和它进行比较的i,j以及返回的next函数的类型都要声明为size_t类型,否则如果是int类型的话,容易发生错误!!!!
在求过next函数然后进行KMP比较的时候,一定更要注意j=0的情况,只有在j大于0的时候,才能调用next函数。否则的话j=0的时候调用next函数只能还是j=0,这个时候i一定要加1,否则的话两个就都不变了!!
不管是计算next函数还是进行KMP算法比较,只要有j=next[j]都要在j>0的情况下才行!!
上面的程序在对二叉树进行序列化的时候是按照先序遍历的顺序放进数组的,并且用#补充了二叉树成为完全二叉树!!!
案例2:给定两个字符串str1和str2,如果str1和str2中出现的字符种类一样且每种字符出现的次数也一样,那么str1与str2互为变形词。请实现函数判断两个字符串是否互为变形词。
举例:
str1="123". str2="231",返回true。
str1="123",str2="2331",返回false。
使用哈希表做字符计数就可以了!!
对str1做字符计数,形成哈希表1;
对str2做字符计数,形成哈希表2;
比对哈希表1和哈希表2的记录是否一致。
可以用固定长度的数组代替哈希表结构,比如字符ascii范围为0-255,就生成256固定长度的数组;时间复杂度为o(n),额外空间复杂度为o(n)。
class Transform {
public:
bool chkTransform(string A, int lena, string B, int lenb) {
// write code here
//下面代表256个ASCII码
char a[256]={0};
char b[256]={0};
int index;
for(int i=0;i
a[index]++;
}
for(int j=0;j
b[index]++;
}
for(int k=0;k<256;k++){
if(a[k]==b[k])
continue;
else
return false;
}
return true;
}
};
string类的length()和size()成员函数都能返回字符串中字符的个数,而且是不包括‘\0’的,但是用sizeof返回的是所占总空间的字节数,是包括字符'\0‘的。但是strlen只能用于char*字符串数组,不能用于string对象!!