学习目标:
60天训练营打卡计划!
学习内容:
LCR 122. 路径加密
- 略简单,和替换空格的难度完全不能比。
class Solution {
public String pathEncryption(String path) {
char[] ch = path.toCharArray();
for(int i = 0; i < ch.length; i++){
if(ch[i] == '.'){
ch[i] = ' ';
}
}
return new String(ch);
}
}
LCR 182. 动态口令
- 和昨天的左旋数组思路完全一致。
class Solution {
private String reverse(String st, int left, int right){
char[] ch = st.toCharArray();
while(left < right){
char tmp = ch[left];
ch[left] = ch[right];
ch[right] = tmp;
left++;
right--;
}
return new String(ch); }
public String dynamicPassword(String password, int target) {
int size = password.length();
return reverse(reverse(reverse(password, 0, target - 1), target ,size - 1), 0, size - 1);
}
}
KMP算法理论学习
可以用于解决在一个串中查找另一个串(模式串)是否出现过问题,非常适用。
- 好难,好懵逼!
- 前缀:包含首字符但不含尾字符的所有子字符串
- 后缀:包含尾字符但不含首字符的所有子字符串
- 前缀表:记录的是模式串(即所求字符串)的子串的最长相等前后缀长度,用于在字符串匹配时做回退(遇到字符不符之后,前缀表可以得到要回退到的位置)的操作。
- 最长相等前后缀长度计算方法:通过下面的示例说明相等前后缀的对比方法,是一个序列的重复出现,而不是反转后的出现。这样的话,我就可以理解next数组的构造过程!
a | b | c | d | d | c | b | a |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
a | b | c | d | a | b | c | d |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
KMP算法:通过计算 模式串 的 子字符串 的 最长相等前后缀长度 得到一个 前缀表(即next数组或者prefix数组) 来 优化匹配 过程的算法。
- 整个KMP算法可以分为:
- 初始化 – i和j指向的元素不相等时的操作(前后缀不相等的情况) – i和j指向的元素相等时的操作(前后缀相等的情况)-- 更新next值
- 可以视为一种双指针的过程。
- 其中i是后缀末尾,i在循环中,一直在向字符串尾移动。
- 其中j是前缀末尾,另一个含义是:i及i之前的字串中最长的相等前后缀长度。 j的移动过程:当i和j所指的值相等时,j+1;当 i 和 j 所指的值不等时,j一直做减法,直到 j == 0 或者 重新和 i 所指值相等。因为最长相等前后缀的匹配规则 (相等指一个序列平移后相等,并非翻转后相等,故最长相等前后缀长度可以从前几位继承),所以 j 同时能有两个含义。
28.找出字符串中第一个匹配项的下标
- 整个KMP算法可以分为:
- 初始化 – i和j指向的元素不相等时的操作(前后缀不相等的情况) – i和j指向的元素相等时的操作(前后缀相等的情况)-- 更新next值
- 可以视为一种双指针的过程。
- 其中i是后缀末尾,i在循环中,一直在向字符串尾移动。
- 其中j是前缀末尾,另一个含义是:i及i之前的字串中最长的相等前后缀长度。 j的移动过程:当i和j所指的值相等时,j+1;当 i 和 j 所指的值不等时,j一直做减法,直到 j == 0 或者 重新和 i 所指值相等。因为最长相等前后缀的匹配规则 (相等 指 一个序列平移后相等,并非翻转后相等,故最长相等前后缀长度可以从前几位继承),所以 j 同时能有两个含义。
class Solution {
private void getNext(String needle, int[] next){
// j是前缀尾部,同时也是长度为j-1的子字符串的最大相等前后缀长度
int j = 0;
next[0] = 0;
// i是后缀尾部
for(int i = 1; i < needle.length(); i++){
// 当 i 和 j 所指的值不等时,j一直做减法,直到 j == 0 或者 重新和 i 所指值相等。
while(j > 0 && needle.charAt(i) != needle.charAt(j)){
j--;
}
// 当i和j所指的值相等时,j+1
if(needle.charAt(i) == needle.charAt(j)) j++;
next[i] = j;
System.out.print(next[i] + " ");
}
}
public int strStr(String haystack, String needle) {
int size = needle.length();
if(size == 0) return 0;
int[] next = new int[size];
getNext(needle, next);
// 使用next数组进行匹配
int j = 0;
for(int i = 0 ; i < haystack.length(); i++){
// 遇到字符不符之后,前缀表可以得到要回退到的位置
while(j >= 1 && haystack.charAt(i) != needle.charAt(j)){
j = next[j-1];
}
if(haystack.charAt(i) == needle.charAt(j)) j++;
// 此时已经找到了第一个匹配项
// +2是因为i相对于j的变化有1的滞后性
if(j == size-1) return i-size+2;
}
// System.out.println("*" + j);
return -1;
}
}
459.重复的子字符串
该题的KMP算法用于在给定的字符串中查找最小的不重复子字符串的长度。
- next[size-1]表示该字符串的最长相等前后缀长度,故 size - next[size-1] 就可以表示最小的不重复子字符串。(科学!) 如下表所示的例子。
a | b | a | b | b | a |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 0 | 1 |
- 对于KMP算法的对于前后缀不相等的情况的处理还是生疏,应该是j = next[j-1],这也是前缀表存在的一个重要意义!自己一定要记好了!
class Solution {
// 怎么找模式串呢?
// 放回值为未重复段长度
private void getNext(String s, int[] next){
// 前缀尾
int j = 0;
next[0] = 0;
// 后缀尾
for(int i = 1; i < s.length(); i++){
// 遇到字符不符之后,前缀表可以得到要回退到的位置
while(j > 0 && s.charAt(i) != s.charAt(j)) j = next[j-1];
if(s.charAt(i) == s.charAt(j)) j++;
// 更新值
next[i] = j;
// System.out.print(next[i] + " ");
}
}
public boolean repeatedSubstringPattern(String s) {
int size = s.length();
int[] next = new int[size];
getNext(s, next);
// next[size-1]该字符串的最长相等前后缀长度,
// 用长度减掉该值即为真实的不重复子字符串长度
if(next[size-1] > 0 && size % (size - next[size-1]) == 0)
return true;
else
return false;
}
}
学习时间:
- 上午一小时,下午两小时(KMP算法),整理文档半小时
- 12月13日重新学习KMP算法,并理清了一刷时的遗留问题。目前可以从根源上理解getNext函数的实现。
- 12月14日第一次自己实现459题,原理还是需要熟悉。