20230919刷题记录
参考代码随想录来刷的。
关键词:字符串、KMP
1 28. 实现 strStr()
力扣题目链接
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1: 输入: haystack = “hello”, needle = “ll” 输出: 2
示例 2: 输入: haystack = “aaaaa”, needle = “bba” 输出: -1
暴力法
一个指针 i 指向主串字符,一个指针 j 指向模式串字符,
比较 i、j 指向的字符,
如果不相等,则 i 指向 i 初始值的下一位;
如果相等,则 i、j 都向后移动一位,再次比较,知道 j 等于模式串的长度或者不相等为止。
时间复杂度显然是O(m*n)。
KMP
思想是让指向主串字符的指针不回溯,只让指向模式串的指针回溯。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
如图所示,第6个元素不匹配,此时主串中第6之前的元素是已知的,因为跟模式串的前6都匹配。故此时让 i 不变,让 j = 3,再次进行比较。
上述的例子中,为什么要让 j = 3 呢?这涉及到next数组。
前缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
例:aab的前缀为
a、ab
后缀
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
例:aab的后缀为
b、ab
最长相等前后缀长度
aab的最长相等前后缀长度为 2。
前缀表
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
例
模式串 | a | a | b | a | a | f |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
前缀表 | 0 | 1 | 0 | 1 | 2 | 0 |
其实感觉前缀表中的数跟下标没有对应关系,例如下标等于4时,前缀表中的数据是2,这个2的含义是当模式串的长度为5时(aabaa),最长相等前后缀的长度是2(aa, aa)。
如何求前缀表
这个给我干懵了。
理解起来很困难。
主要有如下三步:
1 初始化
2 处理前后缀不相同的情况
3 处理前后缀相同的情况
private int[] getNext(String s) {
int[] next = new int[s.length()];
// j 指向前缀的末尾,也表示当前最长相等前后缀的长度
int j = 0;
// i 指向后缀的末尾,同时也在遍历整个模式串。
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
return next;
}
我现在理解了为什么s.charAt(j) == s.charAt(i)
时,要让j++。
理解不了当s.charAt(j) != s.charAt(i)
要令j = next[j - 1];
next数组
好像是有很多版本,都是前缀表的变形,如将前缀表中的所有元素向后移动一位,然后让第一个元素的值等于-1。
代码
class Solution {
public int strStr(String haystack, String needle) {
int i = 0;
int j = 0;
int[] next = getNext(needle);
while (i < haystack.length() && j < needle.length()) {
if (j == -1 || haystack.charAt(i) == needle.charAt(j)) {
++i;
++j;
} else {
if (j > 0) j = next[j - 1];
else j = -1;
}
}
if (j >= needle.length())
return i - needle.length();
else
return -1;
}
private int[] getNext(String s) {
int[] next = new int[s.length()];
// j 指向前缀的末尾,也表示当前最长相等前后缀的长度
int j = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
return next;
}
}
2 232.用栈实现队列
力扣题目链接
感觉很昏,逻辑不是很清晰。
class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
while (!outStack.isEmpty()) {
inStack.push(outStack.pop());
}
inStack.push(x);
}
public int pop() {
while (!inStack.isEmpty())
outStack.push(inStack.pop());
return outStack.pop();
}
public int peek() {
if (!outStack.isEmpty())
return outStack.peek();
while (!inStack.isEmpty())
outStack.push(inStack.pop());
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
3 225. 用队列实现栈
class MyStack {
private LinkedList<Integer> queue1;
private LinkedList<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
LinkedList<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}