kmp算法是字符串匹配经典算法。 字符串匹配算法复杂度为 O(m*n), 而kmp算法可以降到 O(m+n)
算法思路: 这个算法的切入点在于,当我们进行匹配,匹配到某个字符不一样时,前面匹配过的是字符 模式串和主串是一样的,即比如
主串: abaaabab
模式串: abab
假设我们把模式串分为三部分, 最长前缀+中间+最长后缀,(这里最长前缀和最长后缀相等),则会出现两种情况:
如果不存在相等的最长前缀和最长后缀,那么再容易不过了,前面匹配过的字符中不存在模式串的开头,那直接模式串从头开始,与主串下一位匹配。
abbabbbabbb
abbb
bb中不存在开头a,所以直接下一步
abbabbbabbb
a
如果存在,那么就从这个最长前缀下一位开始匹配,即:
abaaabab
abab
下一步:
abaaabab
abab
于是字符串匹配问题的关键就在于next数组怎么求(next数组就是指最长前缀和最长后缀的长度)
求next的思路则是递归思路, 切入点是递归思路,
我感觉这里才是这个算法的难点,很多讲解这个算法思路的,到这一步含糊不清。
要知道长度n的字符串的next,我们只要按知道了 n-1 位的next ,
如果他们下一位相等则+1
如果不等, 比方说:
abab b abab a
到第10位,我们发现前面9位的next也就是4,str[4] != str[10], 即黑色的字母不等
此时说明之前那个前缀必须放弃,选择更短的。 而这里有的信息就是 abab 和 abab 是一样的,那么它的next (aa的next为2),如果和当前位相同,即str[2] == str[10],
abab b abab a
我们可以确保蓝色部分是相等的,所以只要str[2] == str[10], 那么next[10] = next[next[10-1]] + 1;
/**
* next的求法,这里设置next[0] = -1, 可以巧妙地作为一个跳出循环的条件
**/
private static int[] getNext(String str) {
int[] next = new int[str.length()];
next[0] = -1;
for(int i =1; i < str.length(); i++) {
int temp = next[i-1];
while (temp != -1 && str.charAt(temp) != str.charAt(i-1) ) {
temp = next[temp];
}
next[i] = temp + 1;
}
return next;
}
//kmp算法
private static List kmpMatch(String str1, String str2) {
int next[] = getNext(str2);
List pos = new ArrayList<>();
int j = 0;//主串初始位置
int s = 0;//匹配串初始位置
while (j < str1.length()) {
if(s==-1 || str1.charAt(j) == str2.charAt(s)) {
j++;
s++;
if(s == str2.length()) {
pos.add(j-s);
s = 0;
j--; //多加的给减回去
}
}else{
s = next[s];
}
}
return pos;
}
题意: 1234 的旋转词有2341, 3412, 4123,要求判定某个字符串是否为另外一个字符串的旋转词
思路: 将原字符串复制一次加在后面,即 12341234, 然后采用kmp算法,匹配成功与否就是答案。
//旋转词 1234 的旋转词有2341, 3412, 4123
public static boolean Isinverse(String str1, String str2) {
//先判断长度是否相等
if(str1.length() != str2.length()) {
return false;
}
//str1两次合并
String temp = str1+str1;
//判断str2 是否在 str1内
List<Integer> list = kmpMatch(temp,str2);
if(list.size() == 0) {
return false;
}else {
return true;
}
}
题意:将一句英语中的单词逆序,比如“ I love you” 变成 “you love I”
思路:先整个句子字母逆序“uoy evol I” 再每个单词字母逆序, “you love I”
/**
* 单词逆序
*/
private static String inverseWord(String str) {
String temp = inverse(str,0,str.length()-1);
int start = 0;
for(int i=0; iif(temp.charAt(i) == ' ') {
temp = inverse(temp, start, i-1);
start = i+1;
}
}
return inverse(temp,start,temp.length()-1);
}
/**
* 字母逆序
**/
private static String inverse(String str,int start, int end) {
char[] array = str.toCharArray();
for(int i=start,j=end; ichar temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return String.valueOf(array);
}
题意:把字符串左右部分交换,但不能借助其他空间
思路:把整个字符串逆序一次,再把前后两部分分别逆序回去。感觉逆序中也是要借助其他空间啊,呵呵。姑且这么做吧。
/**
* 不借助空间,实现左右部分交换,前n个换到右边去
*/
private static String exchange(String str, int n) {
str = inverse(str,0,n-1);
str = inverse(str,n,str.length()-1);
return inverse(str,0,str.length()-1);
}
注意一个特殊情况就是了。
ba b 虽然 b的字典序小于ba,但是拼接起来最小的是 bab 而不是 bba
所以应该比较 o1+o2 和 o2+o1, 不应该只是 o1 和 o2比较
/**
* 两个字符串拼接字典顺序最小(注意判断方法是 str1+str2 str2+str1)
*/
private static String findSmallest(String[] strs) {
if(strs == null || strs.length == 0) {
return null;
}
Arrays.sort(strs,new MyComparator());
String result = "";
for(int i=0; i< strs.length; i++) {
result+=strs[i];
}
return result;
}
class MyComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
String s1 = o1+o2;
String s2 = o2+o1;
return s1.compareTo(s2);
}
}