https://www.youtube.com/watch?v=GTJr8OvyEVQ
public class Solution {
public int strStr(String haystack, String needle) {
if(haystack.length() < needle.length()) return -1;
if(needle.length() == 0) return 0;
int[] next = getNext(needle);
int q = 0; // number of chars matched in pattern
for(int i = 0; i < haystack.length(); i++){
while(q > 0 && needle.charAt(q) != haystack.charAt(i)){
q = next[q - 1];
}
if(needle.charAt(q) == haystack.charAt(i)){
q ++;
}
if(q == needle.length()){
return i - needle.length() + 1;
}
}
return -1;
}
private int[] getNext(String pattern){
int M = pattern.length();
int[] next = new int[M];
int k = 0; // number of chars matched in pattern
for(int i = 1; i < M; i++){
while(k > 0 && pattern.charAt(k) != pattern.charAt(i)){
k = next[k - 1];
}
if(pattern.charAt(k) == pattern.charAt(i)){
k ++;
}
next[i] = k;
}
return next;
}
}
next[] 里的 k = 正确 match 的长度
next[] 中,每个位置的数字是由 k 赋值的,代表“如果下一个字符串挂了,在我这个位置截止的字符串正确 match 的长度是多少”
于是这个 getNext() 函数就很好解释了。 next[] 的大小等于 pattern 长度,k 初始值为 0.
next[0] = 0 因为 substring 长度如果只为 1 的话,前面没东西和它匹配。
于是开始一个 while 循环,迭代寻找如果当前字符串挂了,我们目前的最长 suffix 到底多长,有可能会跳很多步。这个写法有点类似于 disjoint set 里面 weighted union-find 的 path compression 实现,就是一个 while 循环迭代赋值 index 一直到正确的 / base case 为止。 k > 0 这个条件很重要,不然如果在第一个字符串挂了之后,会去找 next[-1] 就越界了。
每次我们在 index k 上挂的时候,是去找 next[k - 1] 的 k 值是什么。原因是 length 与 index 间有 1 的 offset ,我们去看 index = k 的位置其实是在考虑要不要把 length 设成 k + 1.
此后如果当前字符串匹配,就把 k + 1,赋值到当前 next[i] 上。赋值之后就不会再改了
理解
- next[0] =0 因为substring长度如果只为1的话, 前面没有可以匹配的
- 上图中 j=5, i=8; s[5] 与 s[8]不匹配, 此时去看arr[4]的值为2, 回溯到上一个有可能可以match的地方比如s.substring(0,2) 是"aa". 因为s.subtring(3,5)与s.subtring(6,8)保证匹配, s.substring(3,5)与s.subtring(0, 2)保证匹配, 所以s.substring(0,2)与s.substring(6,8) "aa" 匹配, 继续检查s[2]和s[8]是否匹配. 发现不匹配. 继续回溯,
- 此时 j=2, i=8, 去看arr[1]的值是1, 利用上一步结论, s.substring(0,1)是可以与s.substring(7,8)匹配, 检查是否s[1]与s[8]匹配, 结果匹配, arr[8] = arr[1]+1;
- 如果匹配, 那么i j自增1
public class Solution { public int strStr(String haystack, String needle) { if(haystack.length() < needle.length()) return -1; if(needle.length() == 0) return 0; int[] arr = getNext(needle); int i=0;int j=0; for(;i<haystack.length();i++){ while(j>0 && haystack.charAt(i)!=needle.charAt(j)) j = arr[j-1]; if(haystack.charAt(i)==needle.charAt(j)){ j++; } if(j==needle.length()) return i-needle.length()+1; } return -1; } public int[] getNext(String needle){ int[] arr = new int[needle.length()]; arr[0] =0;int j = 0; int i=1; for(;i<needle.length();i++){ while(j>0 && needle.charAt(i)!=needle.charAt(j)) j = arr[j-1]; if(needle.charAt(i)==needle.charAt(j)) j++; arr[i] = j; } return arr; } }
(G) 面经题http://www.1point3acres.com/bbs/thread-199776-1-1.html
给两个字符串,找到第二个在第一个中第一次出现的位置(自己写string.indexOf这个函数吧),followup1,找一个字符串中period的字符段,followup2,找到period次数最少的,例如abababab,ab出现了4次,abab出现了2次,返回2
Shortest Palindrome
先用two pointers,将原string翻转,two pointers找最长公共suffix/prefix
非常巧妙的思路, str+"#"+str.reverse()来构造string, 打kmp算法的表, 表尾end值代表palindrome的长度
https://leetcode.com/problems/shortest-palindrome/#/solutions
KMP table:
c a t a c b # b c a t a c
0 0 0 0 1 0 0 0 1 2 3 4 5
public class Solution {
public String shortestPalindrome(String s) {
StringBuilder sb = new StringBuilder(s);
sb = new StringBuilder(s+"#"+sb.reverse().toString());
System.out.println(sb.toString());
int[] table = new int[sb.length()];
int j=0; int i=1;
for(;i<table.length;i++){
if(sb.charAt(i)==sb.charAt(j)){
table[i] = table[i-1]+1;
j++;
}
else{
j = table[i-1];
while(j>0&&sb.charAt(i)!=sb.charAt(j)){
j=table[j-1];
}
if(sb.charAt(i)==sb.charAt(j)){
j++;
}
table[i] = j;
}
}
int val = table[table.length-1];
System.out.println(Arrays.toString(table));
StringBuilder ans = new StringBuilder(s.substring(val)).reverse();
return ans.toString()+s;
}
}