题目:
用来求解字符串匹配问题,比如str1中是否包含str2,如果包含就返回str2在str1中开始的位置,不包含返回-1。
思路:
1.在这里我们首先要知道何为最大前缀,何为最大后缀,由下图所示
在这里要用到next数组,何为next数组呢,next数组就是一个存储i位置(i位置之前的)最大前缀和最大后缀匹配长度的数组,比如一个数组[a,b,c,a,b] next值[-1,0,0,0,1],在这里规定最大前缀不包括最后一个字符,最大后缀不包括第一个字符,具体看下图实例讲解以及代码注释
当cn位置的值和i-1不相等时,cn=next[cn]的情况可由下图看出
代码:
public static int KMP(String s1,String s2){
if (s1==null||s2==null||s1.length()<1||s2.length()<1){
return -1;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int i1 = 0;
int i2 = 0;
int[] next = getNextArray(str2);
while (i1<str1.length&&i2<str2.length){
//如果字符串匹配,就继续往下比较
if (str1[i1]==str2[i2]){
i1++;
i2++;
}else if (next[i2]==-1){
//可以看作str1和str2的第一个就不匹配
i1++;
}else {
//求解出next数组后,再比较就相当于把str2的最大前缀的下一个字符和第一个不匹配的字符比较
i2 = next[i2];
}
}
return i2==str2.length ? i1-i2 : -1;
}
public static int[] getNextArray(char[] str2){
if (str2.length==1){
return new int[]{-1};
}
int[] next = new int[str2.length];
//人为规定,next数组0为-1,1为0
next[0] = -1;
next[1] = 0;
int pos = 2;
int cn = 0;
while (pos<next.length){
if (str2[pos-1]==str2[cn]){
//当前位置的next的值为++cn
next[pos++] = ++cn;
}else if (cn>0){
//cn的值变成原cn的next数组的cn值
cn = next[cn];
}else {
//该位置next为0
next[pos++] =0;
}
}
return next;
}
扩展1:
问题:
给定一个字符串str1,只能往str1的后面添加字符变成str2。
要求1:str2必须包含两个str1,两个str1可以有重合,但是不能以同一个位置开头。
要求2:str2尽量短最终返回str2
思路:
1、利用next数组,求出包含字符串str1最后一个字符的最大相同前缀后缀;
例如abracadabra,其最大的相同前后缀是abra,长度是4,
2、向str1尾部添加从最大相同前缀后开始到结尾的子串;
例如,abracadabra+cadabra,即从下标4开始的子串添加到str1末尾,即是所求的str2。
此次的next数组大小不再是str1的长度,而是长度+1,因为要包含最后一个字符。
代码:
public static String answer(String str){
if (str==null||str.length()==0){
return "";
}
char[] c = str.toCharArray();
if (str.length()==1){
return str+str;
}
if (str.length()==2){
return c[0]==c[1 ]? (str+String.valueOf(c[0])) : (str+str);
}
int endNext = endNextLength(c);
return str+str.substring(endNext);
}
public static int endNextLength(char[] chars){
int[] next = new int[chars.length+1];
int pos = 2;
int cn = 0;
next[0] = -1;
next[1] = 0;
while (pos<next.length){
if (chars[pos-1]==chars[cn]){
next[pos++]= ++cn;
}else if (cn>0){
cn = next[cn];
}else {
next[pos++] = 0;
}
}
return next[next.length-1];
}
扩展2
问题:
给定两个二叉树T1和T2,返回T1的某个子树结构是否与T2的结构相等。
思路:
1、将二叉树结构匹配问题转换成字符串匹配问题。
2、二叉树转换成字符串。将二叉树每个节点的值后面都添加一个特殊符号作为划定值边界的符号,如“!”,空节点用另一个特殊符号表示,如“#”,两个二叉树就转换为两个字符串。
eg
3
/ \
2 1
转换完成后就变成"3!2!#!#!1!#!#!"
代码
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isSubtree(Node t1, Node t2) {
String t1Str = serialByPre(t1);
String t2Str = serialByPre(t2);
return getIndexOf(t1Str, t2Str) != -1;
}
//
public static String serialByPre(Node head) {
if (head == null) {
return "#_";
}
String res = head.value + "_";
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
// KMP
public static int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
return -1;
}
char[] ss = s.toCharArray();
char[] ms = m.toCharArray();
int[] nextArr = getNextArray(ms);
int index = 0;
int mi = 0;
while (index < ss.length && mi < ms.length) {
if (ss[index] == ms[mi]) {
index++;
mi++;
} else if (nextArr[mi] == -1) {
index++;
} else {
mi = nextArr[mi];
}
}
return mi == ms.length ? index - mi : -1;
}
public static int[] getNextArray(char[] ms) {
if (ms.length == 1) {
return new int[] { -1 };
}
int[] nextArr = new int[ms.length];
nextArr[0] = -1;
nextArr[1] = 0;
int pos = 2;
int cn = 0;
while (pos < nextArr.length) {
if (ms[pos - 1] == ms[cn]) {
nextArr[pos++] = ++cn;
} else if (cn > 0) {
cn = nextArr[cn];
} else {
nextArr[pos++] = 0;
}
}
return nextArr;
}
public static void main(String[] args) {
Node t1 = new Node(1);
t1.left = new Node(2);
t1.right = new Node(3);
t1.left.left = new Node(4);
t1.left.right = new Node(5);
t1.right.left = new Node(6);
t1.right.right = new Node(7);
t1.left.left.right = new Node(8);
t1.left.right.left = new Node(9);
Node t2 = new Node(2);
t2.left = new Node(4);
t2.left.right = new Node(8);
t2.right = new Node(5);
t2.right.left = new Node(9);
System.out.println(isSubtree(t1, t2));
}