代码随想录:字符串5-7

右旋字符串

题目

        字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。

        例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。

        输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。

        输出:输出共一行,为进行了右旋转操作后的字符串。

代码

import java.util.Scanner;  //Scanner的包要导入,util不要拼写错误

public class Main{
    public static void main(String[] args){
        
        Scanner sc = new Scanner(System.in);  //获取输入,System.in不要忘记写
        int k = sc.nextInt();  //假设=2
        String s = sc.next();  //如abcdefg
        
        StringBuilder sb = new StringBuilder(s);  //StringBuilder的首字母都是大写
        
        reverse(sb, 0, sb.length() - 1);  //翻转整个字符串,如gfedcba
        reverse(sb, 0, k - 1);  //翻转前k个字母,如fgedcba
        reverse(sb, k, sb.length() - 1);  //翻转后面的字母,如fgabcde
        
        System.out.println(sb.toString());
        
    }
    
    //翻转区间[i,j]的字符
    public static void reverse(StringBuilder sb, int i, int j){
        while(i < j){
            char tmp = sb.charAt(i);
            sb.setCharAt(i,sb.charAt(j));  //setCharAt函数首字母C和A是大写
            sb.setCharAt(j,tmp);
            i++;
            j--;
        }
    }
}

代码(直接append到StringBuilder)

import java.util.Scanner;


class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int k = sc.nextInt();
        String s = sc.next();
        StringBuilder sb = new StringBuilder();
        for(int i=s.length()-k; i < s.length(); i++){
            sb.append(s.charAt(i));
        }
        for(int i=0; i < s.length()-k; i++){
            sb.append(s.charAt(i));
        }
        System.out.println(sb.toString());
    }
}

总结

1.思想

        题目要求右旋字符串,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。那么可以分为3个步骤,首先翻转整个字符串,变成"gfedcba",然后,翻转前k个字符,变成"fgedcba",最后,翻转后面剩下的字符,变成"fgabcde"。

2.算法流程

        首先,需要先用Scanner获取输入的k和字符串s,然后new一个StringBuild对象,在其上进行三种reverse的操作(因为String类型不能修改)。接着,就是分别调用3次reverse函数,区别分别是[0,s.length()-1],[0,k-1],[k,s.length()-1]。

        reverse函数我写的是左闭右闭区间,接收StringBuild对象sb,区间开始i和区间结束j三个参数。当i<j时,就一直交换首尾的字符,交换结束后把i++,j--即可。

3.语法问题

(1)import java.util.Scanner;  //Scanner在util包下,要记得导入,util不要拼写错误

(2)Scanner sc = new Scanner(System.in);  //用于获取输入,括号的System.in不要忘记写

(3)new StringBuilder(s);  //StringBuilder的首字母都是大写

(4)sb.setCharAt(i,sb.charAt(j));  //setCharAt函数首字母C和A是大写

第二次刷题总结

        算法没问题。小问题是,在public static void main(String[] args)主函数后边的自定义函数,需要声母为静态函数public static void reverse,这个static不能漏。核心原因是main方法是static,他只能访问本类中的其他静态成员。

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

题目

        给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 

代码(KMP)

class Solution {
    public int strStr(String haystack, String needle) {

        //如果模式串为空,默认返回0
        if(needle.length() == 0){
            return 0;
        }

        //如果模式串长度大于文本串长度,肯定找不到,直接返回-1;
        if(needle.length() > haystack.length()){
            return -1;
        }

        int[] next = new int[needle.length()]; //创建前缀表next
        getNext(next,needle);  //计算next,方便不匹配时进行回退

        //遍历文本串
        int j = 0; //j代表模式串的下标
        for(int i=0;i < haystack.length();i++){

            //当文本串的字符和模式串的字符不相等,即出现冲突了,j要回退
            while(j > 0 && haystack.charAt(i) != needle.charAt(j))
            {
                j = next[j - 1];
            }
            //当文本串的字符和模式串的字符相等,继续向后遍历
            if(haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            //如果模式串已经成功匹配到最后,直接返回匹配下标
            if(j == needle.length()){
                return i - needle.length() + 1;
            }
        }
        return -1;  //如果前面没有匹配,最后返回-1
    }
    
    //计算前缀表next的值
    public void getNext(int[] next,String s){

        //初始化
        int j = 0; //j代表前缀末尾,j+1也就是相同前后缀长度了
        next[0] = 0; //第一个字母没有相同前后缀

        //i代表后缀末尾,从下标1开始变量模式串
        for(int i = 1;i < s.length();i++){

            //如果前后缀不相等,j就要循环回退直到j=0
            while(j > 0 && s.charAt(i) != s.charAt(j)){
                //next[j - 1]代表j前面的字符串的最长相同公共前后缀长度
                j = next[j - 1];  //next[j - 1]前的字符代表已经匹配上的
            }
            //如果前后缀相等
            if(s.charAt(i) == s.charAt(j)){
                j++; //j表示的是前缀末尾,其+1正好等于最长相同公共前后缀长度
            }
            next[i] = j; //更新next前缀表
        }

    }
}

代码(暴力解法+substring+equals)

class Solution {
    public int strStr(String haystack, String needle) {
        int len1 = haystack.length();
        int len2 = needle.length();
        for(int i=0; i <= len1 - len2; i++){
            String sub = haystack.substring(i,i+len2);
            if(sub.equals(needle)){
                return i;
            }
        }
        return -1;
    }
}

代码(暴力解法,手动匹配)

class Solution {
    public int strStr(String haystack, String needle) {
        int len1 = haystack.length();
        int len2 = needle.length();
        for(int i=0; i <= len1 - len2; i++){
            int start1 = i;  //匹配串下标
            int start2 = 0;  //模式串下标
            while(true){
                if(haystack.charAt(start1) == needle.charAt(start2)){  //匹配,后移
                    start1++;
                    start2++;
                }
                else{  //不匹配,直接退出,从匹配串下一个i重新匹配
                    break;
                }
                if(start2 == len2){ //模式串移到最后,找到了,返回下标
                    return i;
                }
            }
        }
        return -1;
    }
}

总结

1.思想

(1)为什么用KMP算法?

        KMP主要用于解决的问题是,判断一个文本串中是否包含另一个模式串。这题就是KMP的经典题目。

(2)KMP的核心思想?

        如果用暴力解法,判断一个文本串中是否包含另一个模式串时,如果出现匹配失败时,会回跳到模式串的首字母重写匹配,时间复杂度是O(m*n)。而KMP通过计算了一个前缀表next,当出现匹配失败时,不会回跳到模式串的首字母,而是直接回跳到上一个匹配的子串后边继续匹配

        那为什么KMP可以出现不匹配的情况,可以直接定位到上次匹配的子串然后继续匹配呢?是因为它提前计算的一个next数组起了作用。

        那什么是next数组?其实,next数组是以模式串各个字符串为结尾的,每个子串的最长相等前后缀的集合。举个例子,假设s = aabaaf,next数组=010120,具体计算过程如下。

        s = a,没有前缀和后缀,next值=0

        s = aa,前缀=a,后缀=a,next值=1

        s = aab,没有相同的前缀和后缀,next值=0

        s = aaba,前缀=a,后缀=a,next值=1

        s = aabaa,前缀=aa,后缀=aa,next值=2

        s = aabaaf,没有相同的前缀和后缀,next值=0

        现在我们已经计算得到了next数组,那么next如何使用,帮助我们在出现不匹配的情况下回退到上一个匹配的位置呢?继续举例,假设文本串=aabaabaaf,模式串=aabaaf,使用过程如下。

        对于模式串的前五个字母aabaa,我们成功匹配。        

        对于模式串的第六个字母b≠f,出现不匹配了,那么我们希望模式串能够回退到上一次匹配的位置是b。为什么是b,看下图就很清楚了,由于b和f前面都匹配,所以A2=A3,又由于最长相同前后缀,所以A1=A2,因此A1=A3,即模式串的前两个字母aa已经匹配成功了,我们只需要从模式串的第三个字母b后面继续和文本串匹配就行。

        先说结论,让j回退的方法是让j等于其前一个下标的next数组值,即j=next[j-1]。为什么用这个公式就能当j回退到上一个匹配位置后边字母呢?next数组我们前面已经计算完成,如下图红色字迹。而字母f的前一个下标的next存储的是2,2代表f字符串前面的aabaa相同前缀A1的长度是2,正好b的下标就是2,所以当j指向f出现不匹配时,我们通过修改j=next[j-1],让j回退当上一个满足匹配的子串后边继续匹配。

        这样,当出现不匹配要回退时,关于next数组的使用方法,以及为什么可以通过next数组进行回退就说完了。

2.算法流程

(1)如何计算next数组

        前面原理我只说了手动计算next数组的方法,而代码要如何实现呢?

        主要分为三个步骤,初始化+前后缀不相同处理+前后缀相同处理。接下来我一步步说明每个步骤的流程。

        首先是初始化,我们令next[0] = 0,因为第一个字母没有相同前后缀。令j = 0,用j代表前缀的末尾字母的下标,因此j+1也就是前缀长度了。

        然后我们用i代表后缀末尾字母的下标,遍历模式串s,s的区间范围是[1,s.length()),这里的i从i开始,是因为next[0]已经初始化好了,后缀至少从index=1开始。

        注意,一定要记住我们用用j代表前缀的末尾字母的下标,用i代表后缀末尾字母的下标!!!

        接下来,我们会判断i和j下标指向的是不是相同的字母,根据前缀字母和后缀字母相同和不相同分别处理。

        如果前后缀i和j指向的字母相同,就让j++,i++。同时令next[i] = j,更新next数组。为什么要让j++呢?因为j指向的是前缀末尾的下标,其+1,就相当于前缀的长度,next[i] = j就让当前i的保存的相同前后缀长度等于j表示的前缀长度。然后i++,继续向模式串后面遍历。(此时j指向的是相同前缀字母的下一个位置,如果下一次字母比较的结果仍是相同,next[i+1]会等于j+1,即next[i]+1。)

        如果前后缀i和j指向的字母不相同,j就要循环回退直到j=0,回退的公式是j = next[j - 1],next[j - 1]代表j前面的字符串的最长相同公共前后缀长度。

        这样,关于next数组计算的流程就说完了,如果还是有点晕,可以把这里的算法流程和前面原理部分手算next数组的例子结合看,再理解一下。

(2)next如何使用

        这里说一下计算完next数组后,我们如何调用使用它。

        首先假设文本串是s,模式串是t。先处理两种特殊情况。如果模式串长度为0,说明模式串为0,直接return 0。如果模式串t的长度大于文本串s,肯定找不到匹配情况,直接return -1。

        接下来,我们初始化j=0,表示模式串的下标,初始化i=0,表示文本串的下标。令i遍历for循环遍历文本串s,进行s[i]和t[j]的判断。

        如果s[i] != t[j],说明文本串的字符和模式串的字符不相等,即出现冲突了,j要循环回退,回退公式是 j = next[j - 1]。

        如果s[i] == t[j],说明当文本串的字符和模式串的字符相等,让i++,j++,继续向后遍历。

        然后,需要判断模式串是否已经成功匹配到最后,如果j == t.length()说明匹配成成功了,直接返回匹配下标i - t.length() + 1。

        最后,如果for循环结束,还没有return,就说明s中不包含模式串t,我们return -1即可。         

3.注意点

(1)计算next数组是,如果前后缀不相等,j要回退,这里用的是while循环回退。同理,判断s[i]和t[j]如果字母不相同,j也是while循环回退。这里其实我没有理解的很明白,就是先记住它吧!

(2)while循环回退时,有一个条件是j > 0,因为j=0是已经指向模式串首字母了,不用再回退了。

(3)模式串已经成功匹配到最后,直接返回匹配下标i - t.length() + 1,这里如果不确定举个例子就行。

第二次刷题总结

        一次通过了居然!核心还是要理解next数组的计算和作用,用于指向上一次匹配成功的下一位置。

        getNext的三个主要步骤是,初始化+前后缀不相同循环回退+前后缀相同ij++前进。

        匹配遍历的主要步骤是,字母不相同时j循环回退+相同时j前进并判断长度+长度满足就返回索引。

459.重复的子字符串

题目

        给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:

        输入: "abab"

        输出: True

        解释: 可由子字符串 "ab" 重复两次构成。

示例 2:

        输入: "aba"

        输出: False

代码(indexof)

class Solution {
    public boolean repeatedSubstringPattern(String s) {

        //先构造s+s字符串
        StringBuilder sb = new StringBuilder(s + s);

        //去头部和尾部字符
        sb.deleteCharAt(0);
        sb.deleteCharAt(sb.length() - 1);

        //如果去头去尾的sb仍然能匹配出s,就是重复字符串
        if(sb.indexOf(s) == -1){
            return false;
        }
        else{
            return true;
        }
    }
}

代码(contains)

class Solution {
    public boolean repeatedSubstringPattern(String s) {

        StringBuilder sb = new StringBuilder(s+s);
        sb.deleteCharAt(0);
        sb.deleteCharAt(sb.length()-1);
        String ss = sb.toString();
        if(ss.contains(s)){
            return true;
        }
        else{
            return false;
        }
    }
}

总结

1.思想

        题目要求判断一个字符串是不是由重复的子字符串构成的,如s=abababab就是由ab重复构成的。那么如何判断s是否由重复子串组成?答案是,如果s+s拼接后(去头去尾)的字符串中,仍然能包含s,就说明是由重复子串构成。

        这里举两个例子。

        s = abab,s+s = ababababa,去头去尾字符串bababab仍然包含ab,就是重复子串

        s = aba,s+s = abaaba,去头去尾字符串baab中,不包含ab,就不是重复子串

2.算法流程

        首先,创建一个StringBuild对象sb等于s+s,然后执行去头去尾操作,调用deleteCharAt()函数,分别删除索引=0和索引=sb.length()-1的元素。最后,用indexOf函数,判断sb字符串中是否包含s,如果不包含,函数返回索引值是-1,return false,否则就 return true。

3.语法问题

(1)StringBuild如何删除某个index的元素:sb.deleteCharAt(index)

(2)StringBuild如何判断是否包含某个子串并返回第一个匹配的位置:sb.indexOf(s)

4.注意点

        这里我偷懒了,没有用KMP手撕,直接调用了indexOf进行模式串匹配。(我太懒了,KMP太复杂了对我而言)

第二次刷题总结

        删除元素的语法忘记了,sb.deleteCharAt(index)。sb.indexOf(s)函数如果找不到会返回-1,不然会返回找到的索引下标。

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一份介绍八股文的面试指南,针对大厂面试常见题型进行分类,以及给出相应的解答思路。这份资料具有很高的参考价值,在为即将面试的求职者提供指引的同时,也可以让读者对八股文的思维框架和策略有更好的了解。 这份资料主要包括面试题目的主题分类,如动态规划、字符串、树等,每个分类下又相应给出了典型的问题,如“最长公共子序列”、“二叉树的层次遍历”等等。对于每个问题,都给出了具体的思路指导和代码模板,许多题目给出了多种不同的解法,这些解法包括时间复杂度和空间复杂度的优化。 这份资料对于求职者来说,意义重大。在面试中,对于某些问题我们可能没有完全解决,但如果有了这份资料,我们在面试中也会有一个清晰的思考框架和即时可用的代码模板。此外,这份资料同样适用于对算法和数据结构感兴趣的开发者和工程师,对于自学和提高都有帮助。 总之,《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一个非常实用的参考材料,它是广大求职者和程序员不可或缺的工具,值得一读。 ### 回答2: 《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一份由知名博主“代码随想”的知识星球推出的热门资料。该资料主要介绍了大厂面试中常见的八股文,包括但不限于动态规划、双指针、贪心算法字符串操作等。 通过学习该资料,可以系统地学习和掌握各种常见的算法和数据结构,有助于提高自己的代码能力和面试竞争力。此外,资料还给出了一些实际的面试题目例子,对于准备去大厂面试的人来说,是一份非常实用的资料。 当然,要想真正掌握这些算法和数据结构,需要自己的不断练习和实践。只有在解决实际问题的过程中,才能真正体会到这些算法和数据结构的作用和优越性。 总之,该资料对于想要深入了解算法和数据结构、提高面试竞争力的人来说是一份值得推荐的优秀资料。 ### 回答3: 代码随想录知识星球精华-大厂面试八股文v1.1.pdf是一份关于大厂面试八股文的精华资料,它收集整理了各个大厂面试中常见的八股文题目和解题思路,对于准备求职或者升职的程序员来说是一份非常有价值的资料。 该资料中涵盖了常见的算法、数据结构、操作系统、计算机网络、数据库、设计模式等知识点,每个知识点都有详尽的讲解和相应的面试题目,可以帮助面试者全面了解每个知识点的考察方向和难点。 此外,该资料还提供了八股文的解题思路和解题方法,强调了实战经验和面试技巧,对于提高面试的成功率也是非常有帮助的。 总之,代码随想录知识星球精华-大厂面试八股文v1.1.pdf是一份非常实用的面试资料,它帮助面试者深入了解各个知识点的考察方向和难点,提高了应对面试的能力和成功率,对于准备求职或者升职的程序员来说是一份不可或缺的资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

守岁白驹hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值