字符串
字符串转数组
用数组的各种技巧(双指针、滑动窗口等解题)
例题1:基本双指针类操作
class Solution {
public String reverseStr(String s, int k) {
char[] a = s.toCharArray();
for (int start = 0; start < a.length; start += 2 * k) {
int i = start, j = Math.min(start + k - 1, a.length - 1);
while (i < j) {
char tmp = a[i];
a[i++] = a[j];
a[j--] = tmp;
} // 也可以直接reverse
}
return new String(a);
}
}
// split形成新的字符串数组,然后反转即可
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
}
}
class Solution {
public String replaceSpace(String s) {
int length = s.length();
char[] array = new char[length * 3]; // 最坏情况是三倍
int size = 0; // 指针1
for (int i = 0; i < length; i++) {
// 指针2
char c = s.charAt(i);
if (c == ' ') {
array[size++] = '%';
array[size++] = '2';
array[size++] = '0';
} else {
array[size++] = c;
}
}
String newStr = new String(array, 0, size);
return newStr;
}
}
滑动窗口类
待补充
KMP
学习链接
核心点:最长公共前后缀的理解。KMP的主要思想是「当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。」
暴力法的问题在于:回溯的时候一个一个回溯效率太低,通过引入最长公共前后缀,即可一次移动最长公共前后缀长度,从而提升了暴力法的回溯效率。
「前缀表是用来回溯的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配,模式串应该跳到哪个位置。」
扫描二维码关注公众号,回复:
12909145 查看本文章
暴力法:
public class Solution {
public int strStr(String haystack, String needle) {
int L = needle.length();
int n = haystack.length();
for (int start = 0; start < n - L + 1; start++) {
// 回溯时,从start++的位置重新开始匹配,因此效率低
if (haystack.substring(start, start + L).equals(needle)) {
return start;
}
}
return -1;
}
}
KMP算法版本:
class Solution {
public int strStr(String haystack, String needle) {
//两种特殊情况
if (needle.length() == 0) {
return 0;
}
if (haystack.length() == 0) {
return -1;
}
// char 数组
char[] hasyarr = haystack.toCharArray();
char[] nearr = needle.toCharArray();
//长度
int halen = hasyarr.length;
int nelen = nearr.length;
//返回下标
return kmp(hasyarr,halen,nearr,nelen);
}
public int kmp (char[] hasyarr, int halen, char[] nearr, int nelen) {
//获取next 数组
int[] next = next(nearr,nelen);
int j = 0;
for (int i = 0; i < halen; ++i) {
//发现不匹配的字符,然后根据 next 数组移动指针,移动到最大公共前后缀的,
//前缀的后一位,和咱们移动模式串的含义相同
while (j > 0 && hasyarr[i] != nearr[j]) {
// 取要与当前主串位置进行比较的下一个模式串的坐标
j = next[j - 1] + 1;
// 超出长度时,可以直接返回不存在。
// 实际上是通过模式串的下标是否超过主串的长度来控制循环的,相当于一种滑窗
if (nelen - j + i > halen) {
return -1;
}
}
//如果相同就将i、j指针同时后移一下,比较下个字符
if (hasyarr[i] == nearr[j]) {
++j;
}
//遍历完整个模式串,返回模式串的起点下标
if (j == nelen) {
return i - nelen + 1;
}
}
return -1;
}
public int[] next (char[] needle,int len) {
// 定义 next 数组,实际上是一种记忆化
// next数组记录了当前位置不匹配时包含当前字符的最长子串长度
int[] next = new int[len];
// 初始化,模式串从下标0开始存储,那么存储的初始值就为-1,参看帖子部分的介绍
// 取为-1 + 1 = 0,即从第0个位置的元素开始匹配
next[0] = -1;
// 当前next[i]的最长前后缀长度,也是要模式串的坐标与主串当前位置比较的坐标
int k = -1;
for (int i = 1; i < len; ++i) {
// 我们此时知道了 [0,i-1]的最长前后缀,但是k+1的指向的值和i不相同时,我们则需要回溯
// 因为 next[k]就时用来记录子串的最长公共前后缀的尾坐标(即长度)
// 就要找 k+1前一个元素在next数组里的值,即next[k+1]
// needle[i]是当前元素,需要等于
while (k != -1 && needle[k + 1] != needle[i]) {
k = next[k];
}
// 相同情况,就是 k的下一位,和 i 相同时,此时我们已经知道 [0,i-1]的最长前后缀
// 然后 k - 1 又和 i 相同,最长前后缀加1,即可
if (needle[k+1] == needle[i]) {
++k;
}
next[i] = k;
}
return next;
}
}