本文根据个人学习过程,积累一些常用知识。供后续参考学习。
字符串的相关的算法,一直是面试和项目中经常用到的。因此,在这里整理记录。字符串可以看成是字符类型的数组,因此可以将其和数组排序、数组查找和顺序调整有关的算法相结合。另外,字符串的比较和子字符串的操作,可以用来解决其他问题,比如一棵树是否和另一棵树的子树相匹配。
字符串操作包含很多基本知识点,如:回文的概念、字符匹配的问题、字典序问题、子序列问题、子串问题、前缀树、后缀树和后缀数组的概念。
另外,动态规划的问题:最长公共子串、最长公共子序列、最长回文子串和最长回文子序列等。
KMP算法、前缀树、后缀树和后缀数组
注:红色区域本文章 没有详细说明,后期会写博客。
一、字符串相关的题目目录
1*、A树的子树拓扑结构是否和B树拓扑结构相同
2、字符串A和B中,字符种类和每种字符出现的次数是否相同
3、字符串旋转词的问题
4*、句子逆序问题
5、字符串左右移位问题
6*、拼接最小字典序字符的问题
7、替换空格问题
8**、最长无重复字符子串问题
二、具体题目详述
1、A树的子树拓扑结构是否和B树拓扑结构相同
描述:对于两棵彼此独立的二叉树A和B,检查A中是否存在一棵子树与B树的拓扑结构完全相同。给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。
分析:普通做法是遍历A中每棵子树判断是否和B树拓扑结构相同,这种方法时间复杂度为O(m*n)。另一种优秀地解决方法,是判断A的前序遍历字符串中是否包含B的前序遍历字符串。将树的问题转化为字符串的问题,用KMP算法将时间复杂度降到O(m+n)。
示例代码:
public boolean chkIdentical(TreeNode t1, TreeNode t2) {
String t1Str = serialByPre(t1); //序列化树的方法
String t2Str = serialByPre(t2);
return getIndexOf(t1Str, t2Str) != -1;
}
// KMP
public int getIndexOf(String s, String m) {
<span style="white-space:pre"> </span>char[] ss = s.toCharArray();
<span style="white-space:pre"> </span>char[] ms = m.toCharArray();
<span style="white-space:pre"> </span>int[] nextArr = getNextArray(ms);
<span style="white-space:pre"> </span>int index = 0;
<span style="white-space:pre"> </span>int mi = 0;
<span style="white-space:pre"> </span>while (index < ss.length && mi < ms.length) {
if (ss[index] == ms[mi]) {
<span style="white-space:pre"> </span>index++;
<span style="white-space:pre"> </span>mi++;
<span style="white-space:pre"> </span> } else if (nextArr[mi] == -1) {
<span style="white-space:pre"> </span> index++;
} else {
<span style="white-space:pre"> </span> mi = nextArr[mi];
}
}
return mi == ms.length ? index - mi : -1;
}
总结:树的子树与另一棵树的拓扑结构匹配问题,转化为字符串的子串问题,使用了经典的KMP算法,时间复杂度为O(m+n),空间复杂度为O(logn)。KMP算法是经常使用的算法,需要学习KMP算法。
2、字符串A和B中,字符种类和每种字符出现的次数是否相同
描述:对于两个字符串A和B,如果A和B中出现的字符种类相同且每种字符出现的次数相同,则A和B互为变形词。请编写实现一个算法,检查两个给定串是否互为变形词。给定两个字符串A和B及他们的长度,请返回一个bool值,代表他们是否互为变形词。
分析:该问题可以使用辅助数组的方式解决。声明一个长度为256的整型数组help,遍历一遍字符串A,可得统计数组A中各个字符出现的次数,记录到help数组中。然后遍历字符串B,将其中每个字符对应help中位置的值做减1操作,当出现数组中某个值小于0时,说明A和B不是变形词,否则,A和B互为旋转词。
示例代码:无
总结:借助辅助数组解决问题是经常使用的方法。辅助数组能够简化问题,降低问题的复杂度。解决常规问题时,除使用数组外,还会使用哈希表、队列、栈等作为辅助工具解决问题。
3、字符串旋转词的问题
描述:如果对于一个字符串A,将A的前面任意一部分挪到后边去形成的字符串称为A的旋转词。比如A=”12345″,A的旋转词有”12345″,”23451″,”34512″,”45123″和”51234″。对于两个字符串A和B,请判断A和B是否互为旋转词。给定两个字符串A和B及他们的长度lena,lenb,请返回一个bool值,代表他们是否互为旋转词。
分析:设A的字符长度为n。由于旋转词的结构特征,可以借助两个A相加得到一个2n长度的字符串C,那么从C下标[0…n-1]中任意位置开始向后截取长度为n的字符串Di,那么Di就是A的旋转词。并且得到的所有的Di集合,就包含了A所有的旋转词。那么就可以将问题转化为B是否是C的子串问题了。同样的,会用到KMP算法。
示例代码:无
总结:无
4、句子逆序问题
描述:对于一个字符串,请设计一个算法,只在字符串的单词间做逆序调整,也就是说,字符串由一些由空格分隔的部分组成,你需要将这些部分逆序。给定一个原字符串A和他的长度,请返回逆序后的字符串。
测试样例:
“dog loves pig”,13
返回结果:”pig loves dog”
分析:解题思路是,首先逆序整个句子,然后逆序每个单词。也可以先逆序每个单词,然后再逆序整个句子。时间复杂度为O(n),空间复杂度为O(1)。
示例代码:
public void rotateWord(char[] chas) {
if (chas == null || chas.length == 0) {return;}
reverse(chas, 0, chas.length - 1);//反转字符串方法
int l = -1;
int r = -1;
for (int i = 0; i < chas.length; i++) {
if (chas[i] != ' ') {
l = i == 0 || chas[i - 1] == ' ' ? i : l;
r = i == chas.length - 1 || chas[i + 1] == ' ' ? i : r;
}
if (l != -1 && r != -1) {
reverse(chas, l, r);
l = -1;
r = -1;
}
}
}
总结:代码中高亮部分统计每次反转的左右边界的技巧,可以解决常规编程中确定边界这种苦恼问题。
5、字符串左右移位问题
描述:对于一个字符串,请设计一个算法,将字符串的长度为len的前缀平移到字符串的最后。给定一个字符串A和它的长度,同时给定len,请返回平移后的字符串。
测试样例:
“ABCDE”,5,3
返回结果:”DEABC”
分析:这一问题跟上一问题属于一类问题,都为旋转问题。解题思路为先旋转左右各部分,然后整体旋转。
示例代码:无
总结:无
6、拼接最小字典序字符的问题
描述:对于一个给定的字符串数组,请找到一种拼接顺序,使所有小字符串拼接成的大字符串是所有可能的拼接中字典序最小的。给定一个字符串数组strs,同时给定它的大小,请返回拼接成的串。
测试样例: [“abc”,”de”],2
返回结果: “abcde”
分析:解决一个误区—常常解决这类问题会陷入一个误区,如给入两个字符串”b”和”ba”,”b”小于”ba”,”b”应放在”ba”的前面的到”bba”,但是,正确的顺序应该是”bab”。重新审视这个题目,我们应该判断字符串A和B的先后顺序时,应当比较A+B和B+A的大小来决定A和B的先后顺序。
示例代码:
public class MyComparator implements Comparator<String> {
public int compare(String a, String b) {return (a + b).compareTo(b + a); }
}
public String findSmallest(String[] strs, int n) {
if (strs == null || n == 0) {return "";}
Arrays.sort(strs, new MyComparator());// 根据新的比较方式排序
String res = "";
for (int i = 0; i < n; i++) {
res += strs[i];
}
return res;
}
总结:学习自定义排序规则。
7、替换空格问题
描述:请编写一个方法,将字符串中的空格全部替换为“%20”。假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由大小写的英文字母组成。给定一个string iniString 为原始的串,以及串的长度 int len,返回替换后的string。
测试样例:”Mr John Smith”,13
返回结果:”Mr%20John%20Smith”
分析:首先得到字符串中空格的个数nzero,然后创建一个长度为len+2*nzero长度的返回字符数组ret。开始从iniString 尾部向前遍历,将字符依次填充到ret的尾部,当遇到空格时,向ret插入%20。遍历完iniString 后,我们就得到了一个符合要求的字符串。
示例代码:无
总结:无
8、最长无重复字符子串问题
描述:对于一个字符串,请设计一个高效算法,找到字符串的最长无重复字符的子串长度。给定一个字符串A及它的长度n,请返回它的最长无重复字符子串长度。保证A中字符全部为小写英文字符,且长度小于等于500。
测试样例:”aabcb”,5
返回结果:3
分析:设置一个变量pre和一个辅助数组map,pre用来记录上一个无重复字符的子串的起始位置,map用来记录字符出现位置。确定了pre和map就可以得到一当前位置字符结尾的无重复字符的子串的长度了。
示例代码:
public int longestSubstring(String A, int n) {
if (A == null || n == 0) {return 0; }
char[] chas = A.toCharArray();
int[] map = new int[256];
for (int i = 0; i < 256; i++) { map[i] = -1; }
int len = 0; int pre = -1;
int cur = 0;
for (int i = 0; i < n; i++) {
pre = Math.max(pre, map[chas[i]]);
cur = i - pre;
len = Math.max(len, cur);
map[chas[i]] = i;
}
return len;
}
总结:确定以字符串中i位置字符结尾的,无重复字符的子串长度。是根据pre和map[arr[i]]两个值较大值决定无重复字符子串的左边界,i作为右边界来完成的。