今日内容
- leetcode. 151 翻转字符串里的单词
- 卡码网. 55 右旋转字符串
- KMP算法
- 字符串总结
- 双指针总结
Leetcode. 151 翻转字符串里的单词
本题需要解决两个问题:
- 如何去除多余空格
- 如何将单词倒序
去除空格可以按照先去除字符串首尾的空格,再去除单词间的多余空格的思路进行。
单词倒序一开始确实不知道该如何处理,看了文章才知道可以先把整个字符串进行翻转,再对单词进行翻转。
按照上述思路,写出代码如下:
class Solution {
public String reverseWords(String s) {
char[] result = deleteSpace(s); // 去除多余空格
reverse(result, 0, result.length - 1); // 将整个字符串翻转
int start = 0;
for (int i = 0; i <= result.length; i++){
// 将单词翻转
if (i == result.length || result[i] == ' '){
reverse(result, start, i - 1);
start = i + 1;
}
}
return new String(result);
}
public char[] deleteSpace(String s){
char[] array = s.toCharArray();
int left = 0;
int right = array.length - 1;
// 去除首尾多余空格
while (true){
if (array[left] == ' '){left++;}
if (array[right] == ' '){right--;}
if (array[left] != ' ' && array[right] != ' '){break;}
}
int fast = left + 1;
int slow = left + 1;
// 去除单词间的多余空格
while (fast <= right){
if (array[fast] == array[fast - 1] && array[fast] == ' '){
fast++;
continue;
}
array[slow] = array[fast];
fast++;
slow++;
}
// 返回去除多余空格后的字符串
char[] result = new char[slow - left];
int index = left;
for (int i = 0; i < result.length; i++){
result[i] = array[index];
index++;
}
return result;
}
public void reverse(char[] array, int left, int right){
while (left < right){
char temp = array[left];
array[left] = array[right];
array[right] = temp;
left++;
right--;
}
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
卡码网. 55 右旋转字符串
本题的意思就是将字符串 s 的后 k 个字符挪到前面去,就好像是经过了旋转一样。
那么本题思路就是先将整体字符串进行翻转,再将前 k 个元素进行翻转,最后把剩余元素再进行翻转,总共翻转 3 次,就可以得到题目想要的效果。
根据上述思路,写出代码:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
String s = sc.next();
sc.close();
char[] array = s.toCharArray();
reverse(array, 0, array.length - 1); // 翻转整体字符串
reverse(array, 0, k - 1); // 翻转前 k 个字符
reverse(array, k, array.length - 1); // 翻转剩余字符
String result = new String(array);
System.out.println(result);
}
public static void reverse(char[] array, int start, int end){
while (start < end){
char temp = array[start];
array[start] = array[end];
array[end] = temp;
start++;
end--;
}
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
KMP算法
文章链接:代码随想录 (programmercarl.com)
KMP算法适用于字符串匹配的问题,它的主要思想是:当进行字符串匹配时,遇到不匹配的情况可以利用之前已经匹配的结果,从而不需要从头匹配,提高了效率。
因此KMP算法中最关键的一点就是:如何知道已经匹配的结果?在KMP算法中,它使用了前缀表来实现这一点。
前缀表
前缀表用于回退,当出现字符串不匹配的情况时,它可以告知下一次该从哪里开始匹配。
在引入前缀表之前,我们先看一下KMP算法在进行字符串匹配时的示意图,该图所描绘的是在文本串中找出是否存在模式串。其中,文本串为 aabaabaafa,模式串为 aabaaf。换言之,文本串相当于仓库,模式串相当于我们要找的货物。
可以看到,当第一次不匹配时,并没有从头开始比较,而是从模式串的第三个字符 ‘b’ 开始比较。这就是前缀表带来的优势。它在碰到不匹配的情况时,可以根据已经匹配的字符串的最长相等前后缀来确定下一次匹配的起点位置。
以该图为例,当碰到不匹配的字符时,它会查看模式串中已匹配字符串(此处为 aabaa)的最长相等前后缀,发现为 aa,也就是说已匹配字符串中最前面的两个字符和最后两个字符是一样的,都是 aa。因此下一次匹配时,就可以跳过前面两个字符,直接从模式串的第三个字符 ‘b’ 开始比较。
这就是前缀表的工作原理,而根据上述文字来看,我们必须知晓模式串的最长相等前后缀才能生成一个前缀表。
最长相等前后缀
何为前后缀?
前缀:不包括最后一个字符的,所有以第一个字符开头的连续子串。
后缀:不包括第一个字符的,所有以最后一个字符结尾的连续子串。
而要求得最长相等前后缀,就需要从一个个子串中遍历得到。下面以图为例。
我们有一个模式串 aabaaf,现在想求得它的最长相等前后缀。首先取长度为 1 的子串。它的前后缀按照定义来讲是没有的 ,所以该字串的最长相等前后缀长度为 0.
再是长度为 2 的子串,它的前缀为 a,后缀为 a,因此最长相等前后缀为 a,长度为 1.
再是长度为 3 的子串,它的前缀为 a、aa,后缀为b、ab,因此最长相等前后缀是没有的,长度为0.
……
根据上述方式类推,最终就可以得到该模式串各个子串的最长相等前后缀了,而这些就对应了前缀表中的元素:
前缀表的使用
当出现不匹配的情况时,就查询该不匹配字符的前一个字符的前缀表元素,该元素指向了开始下一次匹配时的索引。
至于为什么要用前一个字符的前缀表元素,这是因为KMP的思想就是要通过已匹配的字符串信息来提高效率,而不匹配字符的前一个字符刚好是已匹配子串的最后一个字符。
总结
KMP算法目前的介绍文字可能看起来还是比较绕,但是理解它最重要的就是了解最长相等前后缀和前缀表,这两个是实现KMP算法的重要元素。
题目日后再做做,先把KMP的概念理清了先。
字符串总结
该部分完成后,总结起来有两种问题:
- 反转问题
- 匹配问题
反转一般可以用 双指针法 来解决。有一些题目可能会添加一些特殊玩法,但是对反转操作是不影响的,这种主要考验我们的代码能力和逻辑能力。
匹配问题则用 KMP算法 解决,要清楚最长相等前后缀和前缀表的概念,这样才能更好理解KMP算法。
双指针法总结
目前我们碰到的,要使用双指针解决的问题有:
- 数组的元素移除。用到了快慢指针
- 链表的反转、求环。反转就是单纯双指针,求环则用到了快慢指针
- N数之和问题。普通双指针
- 字符串反转。普通双指针
双指针法能有效地提高效率,比如可以将原先O(n ^ 2) 的时间复杂度降到O(n),如果数据规模很大的话,这是非常大的优化了。