代码随想录训练营第九天 | 151. 翻转字符串里的单词 卡码网55.右旋转字符串 28. 实现strStr() 459. 重复的子字符串 字符串总结

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();
    }
}

笨办法的第一次逻辑优化与缺陷:

  1. 没考虑到字符串的开头空格
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();
    }
}

笨办法的第二次优化:

  1. 考虑字符串的前后空格
  2. 将空格一次性遍历完、将单词一次性遍历完。
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();
    }
}

实际考验的算法:

  1. 去除空格
  2. 翻转整个字符串
  3. 翻转每个单词

卡码网55.右旋转字符串(数学推导)

真想取巧的话,直接substring拼接就行hhhh
数学推导:

  1. 反转整体字符串
  2. 反转前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再次进行比较。

下标01234
straabxa
next数组(前缀表不减一)01012
next数组(前缀表减一)-10-101

注意:始终是j+1i两个位置进行比较。

  1. 初始化
    指针i:后缀末尾。初始化为1
    指针j:前缀末尾。初始化为-1(前缀表减一的实现)【因为是前缀末尾,所以也指的是前缀的长度】
    next[0]:初始化为j(因为next[i]表示子串substring(0, i+1)的最长前后缀的长度)
    接下来,遍历i,进行s[i]s[j+1]的比较。
  2. 处理前后缀不同的情况
    如果前后缀末尾不相同,就进行回退:因为比较的是j+1,该位置与i不相同。而next[j]已经求出,因此j回退到next[j]指向的位置。此时前缀仍旧匹配,接下来要比较j+1i
    总之,在后缀末尾i遍历的过程中,一旦不匹配,前缀末尾j就要重来。
    j始终代表匹配上的前缀末尾,j+1是即将匹配的字符。
  3. 处理前后缀相同的情况
    如果前后缀末尾相同,就要进行下一步比较:j++。(在循环遍历时,i才会++)
    同时需要填充next数组,next[i] = j,因为j就表示substring(0, i+1)这个串的最长相同前后缀的长度:前后缀分别是substring(0, j+1), substring(i-(j+1), i)

第一次实现的getNext()

  1. j>=0的判断应该放在while循环中
  2. 为了保证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;
        }
    }

最终:

  1. 求next数组,与求模式串的下标的算法类似,比较的都是j+1i,都是不停回退。区别仅在于,i的遍历的起始位置不同。
  2. 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;
    }
}

字符串总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值