参考文献链接:代码随想录
本人代码是Java版本的,如有别的版本需要请上代码随想录网站查看。
151.翻转字符串里的单词
解题思路
这道题目比较复杂,第一次做的话可能要么想不出思路要么思路不太全导致做的很乱。
这道题简单易懂思路如下:
- 去空格,这包括首尾和中间多余的空格
- 翻转整个字符串
- 翻转每个单词
这样下来就满足题意了。具体细节请看代码里的注释
代码示例
class Solution {
public String reverseWords(String s) {
StringBuilder sb = removeSpaces(s);
reverseString(sb,0,sb.length() - 1);
reverseEachWord(sb);
return new String(sb);
}
//去除空格
public StringBuilder removeSpaces(String s){
int start = 0;
int end = s.length() - 1;
//先通过遍历把首位空格删除(因为没有添加到StringBuilder)
while(s.charAt(start) == ' ') start++;
while(s.charAt(end) == ' ') end--;
StringBuilder sb = new StringBuilder();
while(start <= end){
char c = s.charAt(start);
//如果该位置的字符不是空就加到StringBuilder,如果是空那就判断上一个加入的是不是空,不能连续加两个空格进去。
if(c != ' ' || sb.charAt(sb.length() - 1) != ' '){
sb.append(c);
}
start++;
}
return sb;
}
//翻转字符串
public void reverseString(StringBuilder sb, int start, int end){
while(start < end){
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
//翻转每个单词
public void reverseEachWord(StringBuilder sb){
int start = 0;
int end = 1;
int n = sb.length();
while(start < n){
//start不动移动end,直到end为空或者end遍历到字符串末尾,就进行一次单词翻转,然后更新start位置。
if(end == n || sb.charAt(end) == ' '){
reverseString(sb,start,end - 1);
start = end + 1;
end = end + 2;
}else{
end++;
}
}
}
}
右旋字符串
解题思路
这道题目是比较简单的,new一个新的数组,先把不需要右旋的字符串放入新数组对应位置,给需要右旋的字符串留好前置位置即可。
代码示例
public class Main{
public static void main (String[] args) {
java.util.Scanner scanner = new java.util.Scanner(System.in);
int count = scanner.nextInt();
String s = scanner.next();
char[] chars = s.toCharArray();
char[] newChars = new char[chars.length];
int j = 0;
int i = count;
//先把不需要右旋的放入新数组
while(j < chars.length - count){
newChars[i++] = chars[j++];
}
//把需要右旋的放入
for(int k = 0;k < count;k++){
newChars[k] = chars[j++];
}
System.out.println(new String(newChars));
}
}
KMP算法
在进行下面两道题前需要介绍一下kmp算法。
什么是kmp
从这道题目开始理解,一个字符串aabaabaaf,要找到aabaaf的第一个位置,如果用暴力方式做,就是循环遍历一次一次比较,这样时间复杂度就是O(m*n)。那么如何比较次数更少?
这就是kmp算法:当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
这里你可能还是不懂kmp的意思,我们先介绍一些基础概念。
什么是前缀表
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
因为我们要让比较次数更少,所以我们要利用kmp算法回退到该匹配的位置。
什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
最大相同前后缀
前缀就是不包括尾部的必须包括首部,比如aaf的前缀有a,aa。后缀有f,af。
aa 最大相同前后缀长度就是1
aaa就是2
aab就是0
为什么一定要用前缀表
例如aabaaf,当匹配到f时,上一位a对应的前缀表是2,即从2开始重新匹配,也就是从b开始。
为什么呢? **因为我们是f不匹配,但f前面的aa是匹配的,所以我们跳过头部的aa直接从b重新开始。**这也就是为什么要用前后缀这个东西,后缀和前缀相同,所以后缀刚才判断过了,那么前缀直接跳过即可。
如何计算前缀表
比如aabaaf的前缀表就是 0 1 0 1 2 0
首先开头第一个a,他没有什么前后缀他就一个字母,就是0.
aa的话前最长相等前后缀是a,也就是1
aab没有,是0
aaba最长相等前后缀是a,是1.
aabaa的话是2
前缀表与next数组
在kmp算法中,一般用next数组保存前缀表的数据,方便不匹配时做跳转。那么next数组怎么求?
public int[] getNext(int[] next,String needle){
//指向前缀
int j = 0;
//i指向后缀
for(int i = 1;i<needle.length();i++){
//当前缀和后缀的字符不相等时,j回退到对应位置。
while(j>0&&needle.charAt(j) != needle.charAt(i)){
j = next[j-1];
}
//当前后缀相等时,j加加
if(needle.charAt(j) == needle.charAt(i)){
j++;
}
//给next数组赋值
next[i] = j;
}
return next;
}
如果看了代码看不懂,可以用aabaaf自己演示一遍就好理解了。
一切就绪开始刷题28. 实现 strStr()
具体思路在上述已经讲过了,直接上代码
class Solution {
public int strStr(String haystack, String needle) {
int[] next = new int[needle.length()];
//求出next数组
next = getNext(next,needle);
int j = 0;
//两个字符串一起遍历,相同时就j++,不同时j回退到对应位置,直到j和haystack长度一致说明找到了匹配的位置,返回开头下标即可。
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
//求next数组,
public int[] getNext(int[] next,String needle){
int j = 0;
for(int i = 1;i<needle.length();i++){
while(j>0&&needle.charAt(j) != needle.charAt(i)){
j = next[j-1];
}
if(needle.charAt(j) == needle.charAt(i)){
j++;
}
next[i] = j;
}
return next;
}
}