您的位置:首页 > 其它

算法学习【3】字符串相关含KMP算法

2016-03-10 17:51 507 查看
面试经常会考察String相关问题,现把相关题目整理如下:

1、返回逆序字符串,如,"abc"->"cba",要求空间复杂度为O(1)

思路:首尾字符依次交换

public static char[] inverseOrder(char[] str, int st, int fh){
int len = fh-st+1;
char temp;
for(int i=0;i<len/2;i++){
temp = str[st+i];
str[st+i]=str[fh-i];
str[fh-i]=temp;
}
return str;
}


2、逆序输出单词,如"i am a boy"->"boy a am i",要求空间复杂度为O(1)

思路:首先将整个String逆序,然后每个单词再逆序,或者先将每个单词再逆序,再将整个String逆序

public static String inverseWords(String str){
char[] s = str.toCharArray();
int begin=0;
for(int i=0;i<s.length;i++){
if(s[i]==' '){
inverseOrder(s,begin,i-1);
begin = i+1;
}
}
inverseOrder(s,begin,s.length-1);
inverseOrder(s,0,s.length-1);
return String.valueOf(s);
}


若不是要求额外空间复杂度,用栈Stack更好。

3、给定一个字符串str和一个int N,要求str[0:N]和str[N+i,str.length()-1]左右替换位置,要求额外空间复杂度为O(1)

思路与2中逆序输出单词一样,先将左边str[0:N]逆序,再将右边str[N+i,str.length()-1]逆序,再将整个逆序。

4、判断字符串是否互为旋转词,字符串左边任意长度的子字符转放到右边形成的新字符串即旋转词,
如"abcd"->"bcda"->"cdab"->"dabc"都互为旋转词

要求时间复杂度为O(n)

思路将第一个字符串连续写两次,即"abcdabcd",再判断第二个字符串是否在"abcdabcd"中,匹配的过程可以用"KMP"算法,示例程序暂不用

public static boolean isRotateWord(String w1, String w2){
if(w1.length()!=w2.length())
return false;
String s = w1+w1;
for(int i=0;i<w1.length();i++){
String temp = s.substring(i,i+w1.length());
if(temp.equals(w2)){
return true;
}
}
return false;
}


5、KMP算法做字符串匹配

在第四题中,要求时间复杂度为O(n),若直接用4的代码,肯定不符合。因为每一次temp.equals(w2)时,都要进行w2.length()次操作对比每个字符。

经常需要判断一个长字符串中是否存在指定字符串,即字符匹配问题,一般采用KMP算法。

KMP算法的理解可参考http://www.cnblogs.com/c-cloud/p/3224788.html

这里以示例对KMP算法进行说明:

判断字符串String s = "123512012345"中是否存在子字符串String m = "1234",若存在则返回第一个字符索引,不存在返回-1。

1 2 3 5 1 2 0 1 2 3 4 5
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

若按4中的函数依次判断s的每个substring是否与m相等,过程如上。

第2行与第1行比较后,发现s[3]与m[3]不相等,因此将m后移,已经比较了m的前三位是相等,而由m自身特性可知1与第2位、第3位不相等,因此可以直接将m右移3位,而不用一位一位的移动。

KMP算法的核心就是利用m的自身特性,实现多位移动,利用的特性即 “相同前缀后缀的最大长度”。

m右移位数 = 比较后相同子字符串的位数 - 该相同子字符串对应的部分匹配值
可知第一次,比较后相同子字符串为"123",比较后相同子字符串的位数 = 3,"123"的部分匹配值为0,因此右移3位。

子字符串部分匹配值 = 该子字符串的前缀和后缀中的最长相同字符串位数

以String s = "1231412312",m = "12312"为例说明:

首先创建m各子字符串的部分匹配值表(前缀不包括最后一位、后缀不包括第一位)

“1”:前缀、后缀都为空,部分匹配值=0

“12”:前缀:1,后缀:2,部分匹配值=0

“123”:前缀:1、12,后缀:3、23,部分匹配值=0

“1231”:前缀:1、12、123,后缀:1、31、231,最长相同字符串为“1”,部分匹配值=1

1 2 3 1 4 1 2 3 1 2
1 2 3 1 2
1 2 3 1 2
1 2 3 1 2
1 2 3 1 2
第1次比较:比较后相同子字符串“1231”的位数 = 4,该相同子字符串对应的部分匹配值
= 1,m右移位数 = 4 - 1 = 3

第2次比较:比较后相同子字符串“1”的位数 = 1,该相同子字符串对应的部分匹配值
= 0,m右移位数 = 1

第3次比较:第一位不相等,后移1位

public static int getIndexOf(String s, String m) {
if (s.length() < m.length()) {
return -1;
}
char[] ss = s.toCharArray();
char[] ms = m.toCharArray();
int[] next;

if(ms.length == 1)
next = new int[]{-1};
else{
next = new int[ms.length];
next[0] = -1;
next[1] = 0;
int pos = 2;
int cn = 0;
while (pos < next.length) {
if (ms[pos - 1] == ms[cn]) {
next[pos++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[pos++] = 0;
}
}
}
int si = 0;
int mi = 0;
while (si < ss.length && mi < ms.length) {
if (ss[si] == ms[mi]) {
si++;
mi++;
} else if (next[mi] == -1) {
si++;
} else {
mi = next[mi];
}
}
return mi == ms.length ? si - mi : -1;
}


6、给定一个字符串数组,如{"abc", "def"},将该字符串数组中的字符串拼接起来,如"abcdef"或"defabc",使得拼接的字符串在字典中顺序最小。

要求时间复杂度为O(nlogn)

例如{"b", "ba"},结果为"bab"

思路:先拼接再比较

7、判断字符串是否互为变形词。若两个字符串中出现的字符种类相同且每种字符出现的次数相同,则互为变形词。

思路1:用HashMap表对两个字符串统计不同字符个数

思路2:使用固定长度的数组来实现,定义成256或65536的数组来统计

import java.util.*;

public class Main {
public boolean chkTransform(String str1, int a, String str2, int b) {
if (str1 == null || str2 == null || str1.length() != str2.length()) {
return false;
}
char[] chas1 = str1.toCharArray();
char[] chas2 = str2.toCharArray();
int[] map = new int[256];
for (int i = 0; i < chas1.length; i++) {
map[chas1[i]]++;
}
for (int i = 0; i < chas2.length; i++) {
if (map[chas2[i]]-- == 0) {
return false;
}
}
return true;
}
}


8、找出字符串的最长不重复子串,输出最长不重复子串长度,要求时间复杂度为O(n)。

这里不重复子串的意思是子字符串没有相同字符,面试时一时理解错误,哎。

可以参考http://blog.csdn.net/simmerlee/article/details/40953695

思路:字符在a~z之间,定义一个数组A[26]存放字符最新索引。要求时间复杂度为O(n)即进行一次循环,循环变量即字符索引,在循环里利用两个变量,一个变量begin表示“当前不重复子字符串的起始索引”,一个变量count统计当前不重复子字符串的长度。遇到第一次出现的字符,更新数组A[26]中字符最新索引号,count++;遇到重复字符,判断该字符前一次出现位置是否大于begin,若大于begin,则本次不重复子字符串统计结束,更新begin、count、A,若小于begin,则继续更新A、count++。用Java写的程序如下:

public static String lenOfUnreSubstr(String str){
int[] A = new int[26]; //字符在a~z之间,数组存放最新索引号
for(int i=0;i<26;i++)
A[i] = -1;

int maxbegin = 0;   //最长不重复子字符串的起始索引
int maxlen= 0;      //最长不重复子字符串的长度
int count = 0;      //当前不重复子字符串的长度
int begin = 0;      //当前不重复子字符串的起始索引
int temp;
for(int i=0;i<str.length();i++){
temp = str.charAt(i)-'a';
if(A[temp]==-1){              //第一次出现的字符,存放索引号
A[temp] = i;
count++;
}else{                        //遇到重复字符
if(maxlen<count){
maxbegin = begin;
maxlen = count;
}
if(A[temp]>=begin){       //该重复字符的前一出现在当前不重复子字符串内部
begin = A[temp]+1;
count = i-begin+1;
A[temp] = i;
}else{                    //该重复字符的前一不在当前不重复子字符串内部
A[temp] = i;
count++;
}
//System.out.println(i+" "+begin+" "+A[temp]+" "+maxlen);
}
}
if(maxlen<count){
maxlen = count;               //若最后一串不重复子字符串最长
}

return str.substring(maxbegin, maxbegin+maxlen);
}
运行结果

abcbec:abc

adabcbec:dabc

abadadabbc:bad

ffdeefghff:efgh

abcbecghijkl:abcbecghi

9、给定一个字符串,输出最长的重复子串

可以参考http://dsqiu.iteye.com/blog/1701324

可以利用KMP算法中,局部匹配值表的最大值就是对应的重复子串。

10、判断一个二叉树是否为另一个二叉树的一部分

思路:将大的二叉树序列化按LNR、NLR等方式转化成字符串,然后用上面判断KMP算法判断是否为子字符串。

11、大整数数相乘

可以参考http://www.tuicool.com/articles/zy6vim

思路:分治思想以及乘法规律AB * CD = AC (AD+BC) BD
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: