右旋字符串
题目
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 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--;
}
}
}
总结
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是大写
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前缀表
}
}
}
总结
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,这里如果不确定举个例子就行。
459.重复的子字符串
题目
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。
示例 2:
输入: "aba"
输出: False
代码
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;
}
}
}
总结
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太复杂了对我而言)