3.21---字符串题目(KMP算法)

一、右旋字符串

 但是要求不能申请额外空间,只能在本串上操作:

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        in.nextLine();
        String s = in.nextLine();
        s = rightreverse(s,n);
        System.out.println(s);
    }
    public static String rightreverse(String s, int n) {
        return s.substring(s.length()-n)+s.substring(0,s.length()-n);
    }
}

注意substring函数左闭右开。 

还有一种思路是,先将整个字符串反转,然后分别将前n个字符和后length-n个字符串反转,相当于调用三次反转函数(双指针,可以用位运算,也可以用中间值temp换),具体代码如下:

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = Integer.parseInt(in.nextLine());
        String s = in.nextLine();

        int len = s.length();  //获取字符串长度
        char[] chars = s.toCharArray();
        reverseString(chars, 0, len - 1);  //反转整个字符串
        reverseString(chars, 0, n - 1);  //反转前一段字符串,此时的字符串首尾尾是0,n - 1
        reverseString(chars, n, len - 1);  //反转后一段字符串,此时的字符串首尾尾是n,len - 1
        
        System.out.println(chars);

    }

    public static void reverseString(char[] ch, int start, int end) {
        //异或法反转字符串,参照题目 344.反转字符串的解释
        while (start < end) {
            ch[start] ^= ch[end];
            ch[end] ^= ch[start];
            ch[start] ^= ch[end];
            start++;
            end--;
        }
    }
}

 二、找出字符串中第一个匹配项的下标

一开始的想法;

class Solution {
    public int strStr(String haystack, String needle) {
        char chars1[] = haystack.toCharArray();
        char chars2[] = needle.toCharArray();
        if (chars1.length < chars2.length) return -1;
        int cnt ;
        for (int i = 0; i < chars1.length; i++) {
            cnt=0;
            while (chars1[i] == chars2[cnt]) {
                i++;
                cnt++;
                if (cnt == chars2.length) return i-cnt;
            }
        }
        return -1;
    }
}

 但是在这个例子中执行出错:

发现问题在于,当匹配到issi时,p不匹配,于是needle字符串回到一开始的i开始扫描,但是haystack字符串时是继续刚刚的位置missi,从下一个s开始扫描,就出现错误。于是借鉴代码随想录的想法。 

KMP算法:

 目的:解决字符串的匹配问题。

时间复杂度:n为文本串长度,m为模式串长度,整个KMP算法的时间复杂度是O(n+m)的。

前缀:包含首字母不包含尾字母的字符串

例如:{a,aa,aab,aaba,aabaa}

前缀:包含尾字母不包含首字母的字符串

例如:{f,af,aaf,baaf,abaaf}

最长相等前后缀:看前缀后缀有没有相等元素

例如:{aaba--->1(a),aabaa--->2(aa),aabaaf--->0}

前缀表:每一个位置数字表示:整个字符串当前位置之前的字符串的最长相等前后缀长度。

例如:整个字符串为:aabaaf,那么其对应的前缀表是010120。

为什么需要前缀表,是因为在我们设计字符串匹配的时候,(假如说有一个字符串叫in,我们要在另一个字符串out里面找它)如果In的前面一大部分都和out里面某一部分匹配但是就最后几个字符不匹配,那我们就要在out里面回退,返回上次匹配的开头,从下一个字符再重新匹配in,但是这样很浪费时间,因为我们已经在out里匹配in的一部分字符串了,我们可以回退到某一个位置,匹配之前不匹配的那部分字符串即可。那么这个恰当的位置怎么找呢?

例如这两个字符串,已知 aabaaf对应的前缀表是010120,现在扫描到f(模式串)发现不匹配,这时文本串扫描到b,那么我们找f的前一个字符a所对应前缀表的值2,于是下一次回退到aabaaf中索引为2的位置,即从b(模式串)开始在文本串的位置(b)继续往后匹配。

还有一种匹配方式,是得到 aabaaf对应的前缀表后,将该表右移,前面空位补为-1,比如现在前缀表是010120,那么右移之后就变成-101012,这时扫到f(模式串)发现不匹配,这时文本串扫描到b,那么我们找f所对应前缀表的值2,于是下一次回退到aabaaf中索引为2的位置,即从b(模式串)开始在文本串的位置(b)继续往后匹配。--->next数组

j指向前缀末尾位置,i指向后缀末尾位置。

class Solution {
    //前缀表(不减一)Java实现
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);//得到前缀表

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {//开始扫描haystack
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) //扫到哪处不相同,得到当前前缀表的上一位,将needle索引设置为前缀表上一位的值
                j = next[j - 1];
            if (needle.charAt(j) == haystack.charAt(i)) 
                j++;
            if (j == needle.length()) 
                return i - needle.length() + 1;
        }
        return -1;

    }
    
    private void getNext(int[] next, String s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {//i代表后缀表末尾(后缀不断增大)
            while (j > 0 && s.charAt(j) != s.charAt(i)) 
                j = next[j - 1];//如果不相同,回退,即最大相等前后缀长度减一
            if (s.charAt(j) == s.charAt(i)) //相等先累计加一
                j++;
            next[i] = j; //将当前最大相等前后缀长度记录下来
        }
    }
}

 三、重复的字符串

暴力解法: 

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String s1 = in.nextLine();
        System.out.println(repeatedSubstringPattern(s1));
    }
    public static boolean repeatedSubstringPattern(String s) {
         int zhizhen1 = 0, zhizhen2;
         for (int i = 1; i <= s.length()/2; i++) {
             zhizhen2 =  zhizhen1 + i;
             while(s.charAt(zhizhen1) == s.charAt(zhizhen2)){
                 if(zhizhen2 == (s.length()-1)){
                     if(s.length()%i ==0) return true;
                    //注意这里两个if条件如果写在一起(if1&&2)测试aabaaba就会出现超出索引错误
                     else return false;
                 }
                 zhizhen1++;
                 zhizhen2++;
             }
             zhizhen1 =0;
         }
        return false;
    }
}

还可以用之前的KMP算法:

如果某字符串是重复字符串,那么除去其最长相等前后缀得到的字符串就是最小重复字符串,具体推导可以参考;

代码随想录

class Solution{

  public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
        int j =0;
        next[0] = 0;
        int [] next = new int[s.length()];
        for(int i = 1;i < s.length(;i++){
            while(j > 0 && s.charAt{i} != s.charAt(j)){
                j = next[j-1];
            }
            if(s.charAt{i} == s.charAt(j)) j++;
            next[i] = j;
        }
        if(next[n-1] > 0 && n%(n-next[n-1]) == 0) return true;
        else return false;

    }
}

 以上来自于代码随想录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值