KMP算法讲解、Java实现及面试题目

7 篇文章 0 订阅
1 篇文章 0 订阅

KMP算法讲解、Java实现及面试题目

KMP算法讲解

  • KMP用来干什么?

在说KMP算法之前,我们还是先来说一下KMP算法是用来解决什么问题的。

  • 问题:假如现在有两个字符串s1(aabcabced)和字符串s2(abcabce)
    现在要求:s2在s1中第一次出现的下标(上面的例子结果下标就为1)。
    暴力的解法是:遍历一遍s1的所有字符,并且以遍历到的字符为起始与s2进行比对,如果有完全匹配的字符串,则对应的 起始字符下标便是所求的下标;这样的解法时间复杂度是O(N*M)。
    而KMP算法就是对暴力解法的优化。
  • KMP算法

KMP算法优化主要是提出了一个next数组(跟s2有关系),那么这个是用来干什么的呢?
next数组存的是当前字符前的字符串(不包括自己)的最长的 前缀和 后缀 相同的长度(第一个字符和第二个字符在next中规定为-1和0)
假设s2是(abcabce)那么next数组存储对应如下所示:
s2abcabce
下标0123456
next-1000123
(1)现在规定next下标为0时值为-1,下标为1时值为0;因为当字符为第一个字符时,没有前缀。当字符为第二个字符时,前缀和后缀一样所以为0;
(2)当下标为2时,前面的字符串为ab,那么他的前缀为a,后缀为b时不相同,所以赋值为0;
(3)同理当下标为3时也为0;
(4)当下标为4时,b前面的字符串,前缀为a和后缀为a时,相同且是最长的,所以赋值为1
(5)当下标为5时,c前面的字符串,前缀为ab和后缀为ab时,相同且是最长的,所以赋值为2
(6)当下标为6时,e前面的字符串,前缀为abc和后缀为abc时,相同且是最长的,所以赋值为3

求出next数组后,接下来就要进行求解了:
(1)首先初始化两个指针x,y,分别记录两个字符串s1,s2,当前的下标;
(2)当两个下标所对应的字符相同则同时自增
(3)不同,则在next数组中寻找y对应的下标的值(即y对应当前字符的前面字符串的前缀的下标),判断其是否为-1,为-1则y=0,且x与y对应的两个字符都不相同,所以x自增1
(4)若不为-1,则表示y当前字符前面可能还存在前缀,所以在next中查找对应y下标的值,并将其赋给y(因为next中存的是长度,所以换成下标的话则为前缀后面一位),继续从头重复前面的步骤,直到越界。
这样求解的算法复杂度为O(N)

Java代码

求s2字符串的next数组:

/*
	 * 求的是当前字符的最长的前缀和后缀相同的长度例如abcabcd(d的前面的字符串的 前缀和后缀相同的 最长的字符串是abc)
	 */
	public static int[] getNext(char[] str) {
		if(str.length == 1) return new int[] {-1};
		int [] next = new int [str.length];
		next[0] = -1;//初始化next数组的前两个值
		next[1] = 0;
		int i = 2;
		int cn = 0;//记录前一个数的前缀下一个字符的的下标
		while(i < str.length) {
			if(str[i - 1] == str[cn]) {
				next[i++] = ++cn;//两个字符相同则++
			}else if(cn > 0){
				cn = next[cn];//如果不相等,且cn大于0,则将next数组中当前cn下标的值赋给cn,即cn下标的最小前缀
			}else {
				next[i++] = 0;//当cn==0时,当前i对应next为0
			}
		}
		return next;
	}

求解s2字符串在s1字符串中第一次出现的下标:

public static int kmp(String s1, String s2) {
		if(s1.length() < 1 || s2.length() < 1) return -1;
		char[] chars1 = s1.toCharArray();
		char[] chars2 = s2.toCharArray();
		int [] next = getNext(chars2);//获取s2的next数组
		int x = 0;//初始化两个指针
		int y = 0;
		while(x < s1.length() && y < s2.length()) {
			if(chars1[x] == chars2[y]) {//相同则自增一
				x++;
				y++;
			}else if(next[y] == -1) {//找到头了,就重新开始
				x++;
			}else {
				y = next[y];//不同则寻找前一个
			}
		}
		return y == s2.length() ? x - y : -1;
	}

面试题目

京东面试题:给一个原始串例如:abcabc
要求:在原始串后添加字符,使添加字符串最短,并且可以使原字符串出现两次,并且起始位置不同:例如:abcabcabc
解法:此题与上面求next数组同理,可以将原始串类比成,求当前字符的最长的前缀和后缀相同的长度;则最长前缀为abc
所以只需要在原始串的基础上继续添加,abc前缀后面的字符串就可以了。

Java代码

	public static int[] getNexts(char[] str) {
		if(str.length == 1) return new int[] {-1};
		int [] next = new int[str.length];
		next[0] = 0;
		int cn = 0;
		int i = 1;
		while(i < str.length) {
			if(str[cn] == str[i]) {
				next[i++] = ++cn;
			}else if(cn > 0) {
				cn = next[cn];
			}else {
				i++;
			}
		}
		
		return next;
	}
	public static  String remke(String s) {
		if(s.length() <= 0)return "";
		int [] next = getNexts(s.toCharArray());
		
		return s + s.substring(next[next.length - 1], s.length());
	}

进阶题目:判断一棵二叉树是否是另一棵二叉树的子树
解法:将两棵二叉树进行序列化成字符串,将问题变为求一个字符串是否是另一个字符串的子串,然后采用KMP算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值