模板:
KMP:
细节在代码中
看不懂的可以参照:如何更好地理解和掌握 KMP 算法? - 阮行止的回答 - 知乎
https://www.zhihu.com/question/21923021/answer/1032665486
package StringMatch.KMP;import java.util.ArrayList;
import java.util.List;public class KMP {/*** 计算p在s中所有匹配子串的开始位置* @param s 主串* @param p 模式串* @return 所有匹配子串的开始位置*/public List<Integer> search(String s,String p){ArrayList<Integer> ans = new ArrayList<>();char[] sch = s.toCharArray();char[] pch = p.toCharArray();int[] next = buildNext(p);int count = 0;for (int i = 0; i < sch.length; i++) {while(count > 0 && pch[count] != sch[i]){// 回退到上一轮可以复用的前缀的长度count = next[count-1];}if( pch[count] == sch[i]){count++;}if(count == pch.length){ans.add(i-pch.length+1);// 复用整个模式串的k-前/后缀长度count = next[count-1];}}return ans;}private int[] buildNext(String p){char[] ch = p.toCharArray();int plen = ch.length;int[] next = new int[plen];/*注意next[i]的值不可能是i+1,选取整个子串对跳过失败位置没有任何帮助例如abcabcd,匹配到d错了,此时看前方next[5]=3,即前五位正确,并且3前缀=3后缀然后向后挪动三位 也就是用第二个abc去匹配第一个abc但如果选取整个子串 next[5] = 6,这样向后挪动6位,就从d那一位开始匹配了反而错过了第二个abc,导致可能的错误*/next[0] = 0;for(int i=1;i<plen;i++){// 先找到上一位匹配的k的长度int prev = next[i - 1];// 若上一轮匹配的k的后面一位等于当前这一位,相当于可以扩展一位/*例如,abcdabc,i=7,ch[7] = d既然要看这一轮的最大k,那么检查上一轮的k=3,说明ch[0,6]中 ch[0,2] 与 ch[4,6]是相等的这样如果 ch[3] = ch[7] = d,就可以在ch[0,2] = ch[4,6]上扩展一位变成 ch[0,3] = ch[4,7]*/if(ch[prev]==ch[i]){next[i] = prev + 1;}/*若不等于,我们也不一定要从0再开始。举个例子abcabd dd abcab, i=13 ch[13] = c那么 c!= ch [ next[13-1] ] = ch[5] = d但 c = ch[ next[4] ]啊 那么这个4怎么来的?由于next[12] = 5,即ch[0,12]的5前缀等于5后缀 也就是说ch[0,4] = ch[8,12]那么我们想求最大的K,使得 ch[0,k-1] = ch[13-k+1,13]其中这个K很显然<5,不然就直接匹配上了,变成第一种情况了,也就是ch[13] = ch[5]了可以检查 next[4] ,也就是ch[0,4]中的最大K,这里是2 也即 ch[0,1] = ab = ch[3,4]又因为ch[0,4] = ch[8,12] 所以 ch[0,1] = ch[11,12]这个next[ next[13-1] - 1 ] = 2,就是本轮算上ch[13] = c之前的最大匹配长度所以 next[13] = 2+1 = 3可以用反证法证明假设存在3<k<5,使得ch[0,k-1] = ch[13-k+1,13]那么必有 abca = cabc,矛盾*/else{/*如果得知ch[0,next[i-1]-1]中的最大K值,也就是next[ next[i-1]-1 ],为0,*/int pnxtk = next[Math.max(next[i - 1] - 1, 0)];next[i] = 0;if(ch[i]==ch[pnxtk]){next[i] = pnxtk + 1;}}}return next;}}
1. LC 3008 找出数组中的美丽下标Ⅱ
思路比较简单:
- KMP找出所有匹配的a模式串开始索引
- … b模式串 …
- 由于KMP查找是顺序的,所以索引也是顺序的,对于任意一个index∈kmp(a),对kmp(b)二分查找即可
这道题就是教kmp板子的(周赛的时候不会板子直接T了捏
import java.util.ArrayList;
import java.util.List;class Solution {static int interval;public List<Integer> beautifulIndices(String s, String a, String b, int k) {interval = k;char[] sch = s.toCharArray();char[] ach = a.toCharArray();char[] bch = b.toCharArray();ArrayList<Integer> ans = new ArrayList<>();List<Integer> ares = kmp(sch, ach);List<Integer> bres = kmp(sch, bch);if(bres.isEmpty()){return ans;}for (Integer num : ares) {int bs = bs(num, bres);if(check(bs,num)){ans.add(num);}}return ans;}private int bs(int index,List<Integer> bres){int lp,rp,mid,ans;lp = 0;rp = bres.size();ans = -interval-1;while(lp<rp){mid = ((rp-lp)>>>1)+lp;Integer num = bres.get(mid);ans = Math.abs(num-index)<Math.abs(ans-index)?num:ans;if(num==index){return index;}else if(num<index){lp = mid+1;}else{rp = mid;}}return ans;}private boolean check(int i,int j){return Math.abs(i-j)<=interval;}private List<Integer> kmp(char[] sch,char[] pch){ArrayList<Integer> ans = new ArrayList<>();int[] next = buildNext(pch);int count = 0;for (int i = 0; i < sch.length; i++) {while(count > 0 && pch[count] != sch[i]){count = next[count-1];}if( pch[count] == sch[i]){count++;}if(count == pch.length){ans.add(i-pch.length+1);count = next[count-1];}}return ans;}private int[] buildNext(char[] pch){int[] next = new int[pch.length];next[0] = 0;for(int i=1;i<pch.length;i++){int prev = next[i - 1];if(pch[prev] == pch[i]){next[i] = prev+1;}else{int j = next[Math.max(next[i - 1] - 1, 0)];if(pch[j]==pch[i]){next[i] = j+1;}else{next[i] = 0;}}}return next;}
}