KMP 与 KMP求最小循环节 Java 百度笔试题

百度的这道题折腾了我一个月……真的醉了,主要是复习KMP废了好大劲理解。
KMP
1 KMP基本思想是当出现不匹配时,就能知晓一部分文本的内容,它们就是模式字符串的前 j-1 个字符;
2 基本原理就是不用回退文本指针,只要回退模式指针,使用前缀数组来记录模式指针的下一个位置。

KMP子字符串查找
next数组匹配失败时,需要继续在第 i 位比较,但没有回退 i 。

int i = 0, j = 0;
while(i<N && j<M){
	if (j == -1 || pat.charAt(j) == txt.charAt(i))	{i++; j++;}
	else		j = next[j];
}
if (j == m)	return i - j;
else		return -1;

前缀数组的理解
根据《算法导论》,前缀数组就是模式自身的与模式前缀的最长相同数,next数组其实就是前缀数组的各个位右移一位,然后next[0] 赋为-1。为什么next[0]为什么是 -1 呢?
这是因为模式的第 0 个字符和主串的第 i 个字符不匹配时,要从主串的 i+1 重新匹配。既指针 j 退至 -1时,i 和 j 同时增 1。

构造next数组
难点在于怎么构造next数组呢?
因为,前缀数组就是模式自身的与模式前缀的相同数,所以构造过程就是模式对自身的匹配过程。
首先除了next[0] = -1,next[i]其实都应该等于 0,因为总是要从第0位开始重新比较。这是利用当模式第0位和模式自身某一位匹配失败时,j 最后会等于next[0] = -1了,-1+1 = 0 而 i 也增 1,所以next[i+1] = 0。又next[0]我们在构造前就赋值为 -1 了,所以next数组的构造其实利用了已经next自身已经构造好的部分。

//这里从i=0, j=-1开始对自身匹配,因为我们理解了构造过程,从模式的第1位开始对比就行了
next[0] = -1; next[1] = 0;
int i = 1, j = 0, m = pattern.length();
while(i < m-1) {
	if (j == -1 || pattern.charAt(j) == pattern.charAt(i))	next[++i] = ++j;
	else	j = next[j];
}

最后,KMP时间复杂度是O(M+N),KMP适合查找自我重复性的模式字符串,例如111111。但事实!没有用!做笔试的时候很少这种情况,笔试真的要提速率的话是Boyer-moore和Rabin-Karp。

KMP求最小循环节

  • 百度2019春招实习笔试机器学习卷
  • 描述

给定一个仅由小写字母组成的长度不超过1e6的字符串,将首字母移动到末尾并记录所得的字符串,不断重复操作,虽然记录了无限个字符串,但其中不同的字符串数目是有限的,问不同的字符串有多少个?

  • 输入:
    abab
  • 输出
    2

暴力解就不说了,直接hash也是可以的,hash其实就是Rabin-Karp的思想。
这题的重点是要分析出,如果字符串是由一段字符串复制n次形成的,那么不同的字符串的个数就是这段被复制n次的字符串的长度了。而这段字符串就是循环节了。问题其实就是常见的KMP求最小循环节。

之前KMP都是在求字符串的重叠,那么KMP怎么求循环节呢?

  1. 想象一段循环节字符串 abcabc,那么他的next数组是 000012,前缀数组是000123,3就是代表重叠了3位。其实next数组也可以求到0000123,既求前缀数组,只需把while改为while(i< m) 。(原本我们不需要这一位所以没有求出)
  2. 但是重叠不代表循环,只是循环字符串肯定是重叠的。例如abcabcabc,重叠部分是abcabc的,所以循环节是abcabcabc-6,既9-6 = 3。
  3. 有一个特点就是循环字符串长度肯定是循环节长度的整数倍,既长度循环节对取余位0,而重叠就不是了。例如abcabca,长度是7,重叠是4,7-4 = 3,3根本不可能是循环节的长度,7都是一个质数了。而7%3 =1,所以无循环节。

最后我们可以很轻松理解下面的解法

int next[] = new int[pattern.length+1];
next[0] = -1;
int i = 0, j = -1, m = pattern.length();
while(i < m) {//求前缀数组
	if (j == -1 || pattern.charAt(j) == pattern.charAt(i))	next[++i] = ++j;
	else	j = next[j];
}
int temp = m - next[m];//next[m]就是多求的一位
if(m%temp != 0)
	System.out.print(m);
else
	System.out.print(temp);
return 0;

你可能感兴趣的:(leetcode)