151.翻转字符串里的单词 leetcode链接
class Solution {
public String reverseWords(String s) {
s = s.trim();
int j = s.length() - 1,i = j;
StringBuilder res = new StringBuilder();
while(i >= 0){
while(i >= 0 && s.charAt(i) != ' ') i--;
res.append(s.substring(i + 1,j + 1) + " ");
while(i >=0 && s.charAt(i) == ' ') i--;
j = i;
}
return res.toString().trim();
}
}
具体思路如下:
-
去除空格: 首先,使用
trim()
方法去除输入字符串的首尾空格,以简化后续处理。 -
双指针技术:
- 使用两个指针
i
和j
,j
初始化为字符串的最后一个字符索引,i
用于向左搜索当前单词的起始位置。 - 通过不断移动
i
来找到单词的边界,i
指向空格时,j
指向当前单词的末尾。
- 使用两个指针
-
提取单词:
- 当
i
找到一个空格时,使用substring(i + 1, j + 1)
提取当前单词。 - 将提取的单词添加到结果中,并后面加一个空格。
- 当
-
跳过空格:
- 在找到一个单词后,继续向左移动
i
,跳过多个连续的空格,以找到下一个单词的起始位置。i = j 开始新单词的填入工作。
- 在找到一个单词后,继续向左移动
-
构建结果:
- 使用
StringBuilder
来高效构建结果字符串,避免频繁的字符串拼接。 - 最后,使用
trim()
去除结果字符串的首尾空格,并返回最终结果。
- 使用
右旋字符串 卡码网链接
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int n = Integer.parseInt(scan.nextLine());
String s = scan.nextLine();
int len = s.length();
char[] chars = s.toCharArray();
revese(chars, 0, len - 1);
revese(chars, n, len - 1);
revese(chars, 0, n - 1);
System.out.println(chars);
}
public static void revese(char[] ch, int start, int end){
while(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
}
本题思路整体反转加局部反转
28. 实现 strStr() leetcode链接
class Solution {
public int strStr(String ss, String pp) {
int n = ss.length(), m = pp.length();
char[] s = ss.toCharArray(), p = pp.toCharArray();
// 枚举原串的「发起点」
for (int i = 0; i <= n - m; i++) {
// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int a = i, b = 0;
while (b < m && s[a] == p[b]) {
a++;
b++;
}
// 如果能够完全匹配,返回原串的「发起点」下标
if (b == m) return i;
}
return -1;
}
}
本题是KMP 经典题目,第一次接触KMP算法。
459.重复的子字符串
本题算是KMP算法的一个应用,不过 对KMP了解不够熟练的话,理解本题就难很多。
我的建议是 KMP和本题,一刷的时候 ,可以适当放过,了解怎么回事就行,二刷的时候再来硬啃
字符串总结
双指针法
在344.反转字符串 (opens new window),我们使用双指针法实现了反转字符串的操作,双指针法在数组,链表和字符串中很常用。
接着在字符串:替换空格 (opens new window),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
那么针对数组删除操作的问题,其实在27. 移除元素 (opens new window)中就已经提到了使用双指针法进行移除操作。
同样的道理在151.翻转字符串里的单词 (opens new window)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
一些同学会使用for循环里调用库函数erase来移除元素,这其实是O(n^2)的操作,因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。
反转系列
在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。
541. 反转字符串II (opens new window)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
其实当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。
在151.翻转字符串里的单词 (opens new window)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
这道题目通过 先整体反转再局部反转,实现了反转字符串里的单词。
后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。
在字符串:反转个字符串还有这个用处? (opens new window)中,我们通过先局部反转再整体反转达到了左旋的效果。
KMP
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
KMP的精髓所在就是前缀表,在KMP精讲 (opens new window)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。
前缀表:起始位置到下标i之前(包括i)的子串中,有多大长度的相同前缀后缀。
那么使用KMP可以解决两类经典问题:
再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。
前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。
然后针对前缀表到底要不要减一,这其实是不同KMP实现的方式,我们在KMP精讲 (opens new window)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。
其中主要理解j=next[x]这一步最为关键!
总结
字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力。
双指针法是字符串处理的常客。
KMP算法是字符串查找最重要的算法,但彻底理解KMP并不容易,我们已经写了五篇KMP的文章,不断总结和完善,最终才把KMP讲清楚。