151. 翻转字符串里的单词(难:数学推导)
笨办法:
class Solution {
public String reverseWords(String s) {
StringBuilder sb = new StringBuilder();
char[] sArr = s.toCharArray();
int i = s.length()-1;
while(i>=0){
if(sArr[i]==' '){ // 空格 还是那个问题,两个空格,处理前一个还是处理后一个? // 此时不应该i--,而要用while循环,将空格全部处理。
if(sb.length()==0 || sb.charAt(sb.length()-1)==' '){ // 已经有空格了
i--;
continue;
}else{
i--;
sb.append(' ');
}
}else{ // 非空格
int left = i;
while(left>=0 && sArr[left]!=' ') left--;
for(int k=left+1; k<=i; k++){
sb.append(sArr[k]);
}
i=left;
}
}
// 末尾空格
for(i = sb.length()-1; sb.charAt(i)==' '; i--){
sb.deleteCharAt(i);
}
return sb.toString();
}
}
笨办法的第一次逻辑优化与缺陷:
- 没考虑到字符串的开头空格
class Solution {
public String reverseWords(String s) { // 笨办法
StringBuilder sb = new StringBuilder();
char[] sArr = s.toCharArray();
int i = s.length()-1;
while(i>=0){
while(i>=0 && sArr[i]==' '){
// 是多余空格
if(!(i>0 && sArr[i-1]==' ')) {// 是第一空格
if(sb.length()>0) sb.append(' '); // 是否为字符串的末尾空格
// 是否为字符串的开头空格
}
i--;
}
int right = i;
while(i >= 0 && sArr[i]!= ' ') i--;
if(sArr[i+1]!=' '){ // 处理末尾空格
for(int k=i+1; k<=right; k++){
sb.append(sArr[k]);
}
}
}
return sb.toString();
}
}
笨办法的第二次优化:
- 考虑字符串的前后空格
- 将空格一次性遍历完、将单词一次性遍历完。
class Solution {
public String reverseWords(String s) { // 笨办法
StringBuilder sb = new StringBuilder();
char[] sArr = s.toCharArray();
int i = s.length()-1;
while(i>=0){
while(i>=0 && sArr[i]==' ') i--;// 遍历完所有空格
if(sb.length()>0 && i>=0) sb.append(' '); // 保证了去除字符串前后的空格
int right = i;
while(i >= 0 && sArr[i]!= ' ') i--; // 遍历完该单词
for(int k=i+1; k<=right; k++) sb.append(sArr[k]);
}
return sb.toString();
}
}
实际考验的算法:
- 去除空格
- 翻转整个字符串
- 翻转每个单词
卡码网55.右旋转字符串(数学推导)
真想取巧的话,直接substring拼接就行hhhh
数学推导:
- 反转整体字符串
- 反转前k个,反转其他的
import java.util.*;
class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
String s = sc.next();
// 边缘条件
if(k>=s.length()) System.out.println(s);
char[] cs = new char[s.length()];
for(int i=0; i<s.length()+k; i++){
cs[(i+k)%s.length()] = s.charAt(i%s.length());
}
System.out.println(String.valueOf(cs));
}
}
28. 实现strStr()(难:kmp、求前缀表、理解最长相同前后缀)
next数组(前缀表不减一):next[i]表示,子串substring(0, i+1)(i+1代表子串的长度),它的最长相同前后缀的长度。也正因此,可以回退——此时后缀已经匹配上,证明前缀也可以匹配。
求next数组(前缀表减一):本来next[i]==最长相同前后缀长度,现在是next[i]==最长相同前后缀长度-1。例如,主串aabxa,模式串aabaa,在j=2时,j+1指向字符a,此时next[2]=-1。此时,会回退j=-1,而j+1再次进行比较。
| 下标 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| str | a | a | b | x | a |
| next数组(前缀表不减一) | 0 | 1 | 0 | 1 | 2 |
| next数组(前缀表减一) | -1 | 0 | -1 | 0 | 1 |
注意:始终是j+1与i两个位置进行比较。
- 初始化
指针i:后缀末尾。初始化为1
指针j:前缀末尾。初始化为-1(前缀表减一的实现)【因为是前缀末尾,所以也指的是前缀的长度】
next[0]:初始化为j(因为next[i]表示子串substring(0, i+1)的最长前后缀的长度)
接下来,遍历i,进行s[i]与s[j+1]的比较。 - 处理前后缀不同的情况
如果前后缀末尾不相同,就进行回退:因为比较的是j+1,该位置与i不相同。而next[j]已经求出,因此j回退到next[j]指向的位置。此时前缀仍旧匹配,接下来要比较j+1与i。
总之,在后缀末尾i遍历的过程中,一旦不匹配,前缀末尾j就要重来。
而j始终代表匹配上的前缀末尾,j+1是即将匹配的字符。 - 处理前后缀相同的情况
如果前后缀末尾相同,就要进行下一步比较:j++。(在循环遍历时,i才会++)
同时需要填充next数组,next[i] = j,因为j就表示substring(0, i+1)这个串的最长相同前后缀的长度:前后缀分别是substring(0, j+1),substring(i-(j+1), i)
第一次实现的getNext():
j>=0的判断应该放在while循环中- 为了保证
i一定赋上值,因此next[i] = j放在for循环中:若是两指针的字符相等,则赋值;若一直不匹配,则此时j的值为-1,也赋值。
private static int[] getNext(String needle){
int[] next = new int[needle.length()];
int j=-1;
next[0] = j; // 此时,j 表示needle.substring(0, 1)的最长前后缀长度-1,也表示最长前缀的末尾
for(int i=1; i<needle.length(); i++){ // 因为next[0]已经赋值,因此从1开始遍历
while(needle.charAt(i)!=needle.charAt(j+1)){ // 只要不相等,就一直回退。 也因此,要先比较不相等的情况。
if(j>=0) j = next[j]; // 保证不越界
else next[i] = j;
}
if(needle.charAt(i)==needle.charAt(j+1)){ // 比较的是j+1,
j++;
next[i] = j; // 此时,j表示needle.substring(0, i+1)的最长前后缀长度-1,也表示最长前缀的末尾
}
}
return next;
}
随想录中的:
public void getNext(int[] next, String s){
int j = -1;
next[0] = j;
for (int i = 1; i < s.length(); i++){
while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
j=next[j];
}
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
}
}
最终:
- 求next数组,与求模式串的下标的算法类似,比较的都是
j+1与i,都是不停回退。区别仅在于,i的遍历的起始位置不同。 while循环不停确定j的位置,因此不能先判断相等,而是要先判断不相等。最后,要么相等了,要么退回到模式串起始位置。总之,next[i]一定会赋上值。
class Solution {
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
int j = -1;
for(int i=0; i<haystack.length(); i++){
while(j>=0 && needle.charAt(j+1)!=haystack.charAt(i)){
j = next[j];
}
if(needle.charAt(j+1)==haystack.charAt(i)){
j++;
}
if(j==needle.length()-1) return i-j;
}
return -1;
}
private static int[] getNext(String needle){
int[] next = new int[needle.length()];
int j=-1;
next[0] = j;
for(int i=1; i<needle.length(); i++){ // 因为next[0]已经赋值,因此从1开始遍历
while(j>=0 && needle.charAt(i)!=needle.charAt(j+1)){ // 只要不相等,就一直回退。 也因此,要先比较不相等的情况。
j = next[j]; // 保证不越界
}
if(needle.charAt(i)==needle.charAt(j+1)){ // 比较的是j+1,
j++;
}
next[i] = j;
}
return next;
}
}
459. 重复的子字符串(难:数学推导)
看了一遍随想录,直接说证明结果吧:最长相等前后缀不包含的子串的长度 可以被 字符串s的长度整除 <==> 不包含的子串 就是s的最小重复子串。
注意:判断条件
有最长相等前后缀、能整除
class Solution {
public boolean repeatedSubstringPattern(String s) {
int[] next = getNext(s);
int len = s.length();
int maxLen = next[len-1] + 1;
if(next[len-1]>=0 && len % (len - maxLen) == 0) return true;
return false;
}
private static int[] getNext(String needle){
int[] next = new int[needle.length()];
int j=-1;
next[0] = j;
for(int i=1; i<needle.length(); i++){ // 因为next[0]已经赋值,因此从1开始遍历
while(j>=0 && needle.charAt(i)!=needle.charAt(j+1)){ // 只要不相等,就一直回退。 也因此,要先比较不相等的情况。
j = next[j]; // 保证不越界
}
if(needle.charAt(i)==needle.charAt(j+1)){ // 比较的是j+1,
j++;
}
next[i] = j;
}
return next;
}
}
981

被折叠的 条评论
为什么被折叠?



