刷算法Leetcode---4(字符串篇)

文章详细介绍了字符串反转、单词反转以及子字符串匹配问题的多种解决方案,包括双指针法、KMP算法的应用,特别关注于LeetCode中的具体实例和优化策略。
摘要由CSDN通过智能技术生成

前言

        本文是根据代码随想录中的字符串顺序进行编写,只刷了里面力扣的题 代码随想录

        其他文章链接:刷算法Leetcode文章汇总 

字符串篇

344.反转字符串

        ①双指针,前后交换

        ②for循环,s[i] = s[n-i-1],与双指针思想相同

541.反转字符串Ⅱ

        java中字符串不能修改,先转换为char数组

        for循环每2k个字符一组,组内使用双指针反转前k个字符,每次判断右指针是否越界

151.反转字符串中单词

        ①双指针从后往前,for循环找到每个单词的左右下标,逐个将单词加入StringBuilder中

        ②编写char数组反转函数,先将整个字符串去掉头尾空格后反转,再使用双指针逐个找到每个单词的下标,使用反转函数将每个单词反转,然后拼接到新字符串中,并拼接一个空格

        ③使用双端队列Deque,双指针找到每个单词,使用头插法入队,最后用join拼接为String

class Solution {
    public String reverseWords(String s) {
        Deque<String> wordDeque = new ArrayDeque<>();
        s = s.trim();
        int l = 0, r = 0;
        for(;r<s.length();r++){
            if(s.charAt(r)==' '){
                if(s.charAt(r-1)!=' '){
                    wordDeque.offerFirst(s.substring(l,r));
                }
                l = r+1;
            }
        }
        wordDeque.offerFirst(s.substring(l,r));
        return String.join(" ",wordDeque);
    }
}

注意上面三种方法,因为空格问题,都可能需要单独处理最后一个单词。

        ④使用java中的库函数,trim()去除头尾空格,split("\\s+")按空格分割为数组,asList()变为ArrayList,Collections.reverse()反转list,join(" ",wordList)用空格拼接为String并返回,每个函数都可以自己手动实现

class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        List<String> wordList = Arrays.asList(s.split("\\s+"));
        Collections.reverse(wordList);
        return String.join(" ",wordList);
    }
}

28.找出字符串中第一个匹配项的下标

        ①暴力求解,双重循环判断haystack(i+j)和needle(j)

        ②经典KMP算法(之前学了一遍但这次还是看来好几天才看明白...),核心在于模式串pattern的next数组的构建(这里不解释原理和意义,只介绍我自己理解的next数组构建方法)

        next数组有好几个版本,根据next数组后续使用的不同而不同,我采用的是next[0]=0并且next[i]记录[0,i]字符串的最长相等前后缀(即没有进行移位)

        若此时要准备判断pattern[k]和pattern[i],说明已知pattern中[0,k-1]与[i-k,i-1]相等,即为pattern中[0,i-1]中最长相等前后缀,长度为k-1,next[i-1]=k-1,此时进行pattern[k]和pattern[i]的判断:

        a)两字符相等,说明[0,k]与[i-k,i]相等,即next[i]=k,继续往后判断

        b)两字符不等,问题重新转换为原始问题,找到pattern中[0,i]的最长相等前后缀,且前后缀长度<k,问题又转换为pattern中存在[0,k-1]的前缀与[i-k+1,i]的后缀匹配,此时换一个角度考虑,将[0,k-1]看成一个模式串,[i-k+1,i]看成一个目标串,已知pattern[k]与pattern[i]不等,根据next数组,下一个比较pattern[next[k-1]]与pattern[i]是否相等,一直重复,即有k = next[k-1]

class Solution {
    public int strStr(String haystack, String needle) {
        if(haystack.length() < needle.length()) return -1;
        int m = haystack.length(), n = needle.length();
        if(n == 0) return 0;
        int[] next = new int[n];
        getNext(needle, next);
        for(int i = 0, j = 0; i < m; i++){
            while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
                j = next[j-1];
            }
            if(haystack.charAt(i) == needle.charAt(j)) j++;
            if(j == n) return i - n + 1;
        }
        return -1;
    }

    void getNext(String needle,int[] next){
        for(int i = 1, k = 0; i < needle.length(); i++){
            while(k > 0 && needle.charAt(i) != needle.charAt(k)){
                k = next[k-1];
            }
            if(needle.charAt(i) == needle.charAt(k)) k++;
            next[i] = k;
        }
    }
}

        ③java函数,一行代码解决,return haystack.indexOf(needle, 0);

        解释为,在haystack中从下标0开始,寻找第一个与needle匹配的开始下标,否则为-1

459.重复的子字符串

        ①暴力求解,判断每种长度的字符串是否可能,使用boolean记录每次for循环中,是否出现不匹配的情况,s.charAt(j) != s.charAt(j%i)

        剪枝:(a)i的范围可以缩小为[0,n/2]    (b)只要出现不匹配就break

有一种判断方法为,如果s+s中存在一个为s的字串,且不是开头或者结尾,就说明s为满足题目条件的重复子字符串(Leectcode官方解答有充分性和必要性证明,这里不重复证明),根据这个方法,问题转换为判断重复子串,又有了下面三种方法:

        ②与28题相同,java函数一行代码return (s+s).indexOf(s,1) != s.length(),下标从1开始保证不是前一个s,返回的下标不等于s.length()保证不是后一个s

        ③与28题相同,使用KMP算法,详细过程不重复,next数组的构建与之前相同,在KMP匹配时,可以将haystack的下标i修改为[1,m-2],这样就保证了匹配的下标不是开头或者结尾

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        return kmp(s+s,s) != -1;
    }

    public int kmp(String query, String pattern){
        int m = query.length(), n = pattern.length();
        int[] next = new int[n];
        for(int i = 1, k = 0; i < n; i++){
            while(k > 0 && pattern.charAt(i) != pattern.charAt(k)){
                k = next[k-1];
            }
            if(pattern.charAt(i) == pattern.charAt(k)) k++;
            next[i] = k;
        }
        for(int i = 1, j = 0; i < m - 1; i++){
            while(j > 0 && query.charAt(i) != pattern.charAt(j))
                j = next[j-1];
            if(query.charAt(i) == pattern.charAt(j)) j++;
            if(j == n) return i - n + 1;
        }
        return -1;
    }
}

        ④改进KMP算法,如果s存在重复子字符串,那么对于next数组,next[n-1]>0,并且根据前面提出的判断方法,有这个 结论n%(n-next[n-1]-1)==0,详细推导见Leetcode官方最后一种解法(我这里的next数组使用的是28题中介绍的那个版本,官方的不太一样)

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
        int[] next = new int[n];
        getNext(s, next);
        return next[n-1] > 0 && n % (n-next[n-1]) == 0;
    }

    public void getNext(String pattern, int[] next){
        for(int i = 1, k = 0; i < pattern.length(); i++){
            while(k > 0 && pattern.charAt(i) != pattern.charAt(k)){
                k = next[k-1];
            }
            if(pattern.charAt(i) == pattern.charAt(k)) k++;
            next[i] = k;
        }
    }
}

字符串总结

        ①字符串反转问题,基本上使用双指针解决。将字符串每个字符反转,直接用双指针交换;将字符串中的单词反转,双指针用于确定每个单词的位置

        ②字符串单词反转问题,可以使用多次反转,先整个反转,再逐个单词反转

        ③子字符串匹配问题,使用暴力求解或者KMP算法,关键在于next数组的构建

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值