左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
这个题看着就感觉有点简单,就是把前k个拿过来放后面就完事儿了,还整个左旋转,直接用StringBuffer就行,用数组的话应该要开辟两个数组空间
- 自己错代码:
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuffer a = new StringBuffer(s);
int r=s.length();
a.insert(r,s);
a.delete(0,n-1);
//delete方法时左闭右开,所以就是n,不是n-1
a.delete(a.length(),2*a.length()-n-1);
//这算的直接是a了,那能行吗,这里算的是原字符串的长度
return a.toString();
}
}
- 自己正确代码
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuffer a = new StringBuffer(s);
int r=s.length();
a.insert(r,s);
a.delete(0,n);
a.delete(r,2*r-n);
return a.toString();
}
}
官方题解很妙:
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}
其他方法无非就是利用列表或者for循环等
- 代码随想录的方法
先翻转k前面,再反转k后面,再合起来之后一起反转。也挺有意思,这里要学习的是不是在数组进行字符交换,而是如何在StringBuffer或者StringBuilder上直接进行字符串的交换
class Solution {
public String reverseLeftWords(String s, int n) {
int len=s.length();
StringBuilder sb=new StringBuilder(s);
reverseString(sb,0,n-1);
reverseString(sb,n,len-1);
return sb.reverse().toString();
}
public void reverseString(StringBuilder sb, int start, int end) {
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
}
28. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
- 思路就是暴力的去比对、匹配
自己代码:
class Solution {
public int strStr(String haystack, String needle) {
int m=haystack.length(),n=needle.length();
char[] h=haystack.toCharArray(),ne=needle.toCharArray();
for(int i=0;i<=m-n;i++){
//这里要注意,i不是直接小于m,而是要小于等于m-n,如果直接是m的话,最后几个
//在while循环里++的时候会有空指针异常
int b=0,j=i;
while(b<n && h[j]==ne[b]){//这里是且,任意一个条件不满足就跳出来
j++;
b++;
}
if(b==n) return i;
}
return -1;
}
}
但是这个放在在一遍一遍判断i,没有在i+needle.length的下一处重新判断,所以时间久(不对,就while一次,while要是都执行完了说明已经遇见重复的字符串了,不回向下判断,而没执行完,需要从下一个i开始判断)
这里介绍了KMP算法
- 基础原理
KMP算法简单来说就是在一个串中查找是否出现过另一个串,思想就是当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
这里就有了前缀表的定义,前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配,next数组就是一个前缀表
- 前缀表是如何记录的呢?
首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
- 那么什么是前缀表:
记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。前缀表要求的就是相同前后缀的长度。
- 前缀后缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
- 为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配?
下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
- 怎么计算前缀表?
长度为前1个字符的子串a,最长相同前后缀的长度为0。
长度为前2个字符的子串aa,最长相同前后缀的长度为1。
长度为前3个字符的子串aab,最长相同前后缀的长度为0。
长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。 - 那么把求得的最长相同前后缀的长度就是对应前缀表的元素
那模式串和前缀表对应的数字表示的关系就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
- 如图,第二次匹配就从模式串下标为2处也就是b处开始匹配
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。前一个字符的前缀表的数值是2, 所以把下标
移动到下标2的位置继续比配。 最后就在文本串中找到了和模式串匹配的子串了
- 实现上有很多都把前缀表-1作为next数组,其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。
减一后的next数组:
- 如何构造next数组呢?
现在就是自己能想出来next数组,但是next数组到底是怎么实现出来的呢
j指向前缀末尾位置,i指向后缀末尾位置 - 问题:aafaaf 不就1个吗,就是列出他的所有的前缀和后缀序列,只有一个是完全一样的,啊!是记录的是位数吗?如果是位数的话就合理了,感觉这个算法咋怎么想都想不通呢,服了,1.为啥要都-1呢?不减1为啥要空出来个节点呢?为啥不相等的情况,要退到模式串前一个继续比较呢????
不非得想通的话
class Solution {
public int strStr(String haystack, String needle) {
int n = needle.length();
if(n==0)
return -1;
int[] next = new int[n];
get(needle,next);
int j=0;
for(int i=0;i<haystack.length();i++){
while(j>0&&needle.charAt(j)!=haystack.charAt(i)){
j=next[j-1];
}
if(needle.charAt(j)==haystack.charAt(i)){
//next[j]=j;
j++;
}
if(j==needle.length()){
return i-needle.length()+1;
}
}
return -1;
}
private void get(String s,int[] next){
int j=0;
next[0]=j;
for(int i=1;i<s.length();i++){
while(j>0&&s.charAt(j)!=s.charAt(i)){
j=next[j-1];
}
if(s.charAt(j)==s.charAt(i)){
// next[j]=j;
// j++;
j++;
next[i]=j;
}
}
}
}
459. 重复的子字符串
思路:得遍历,从第一个字符开始,找到下一个和第一个字符相同的字符,依次比较
我根本不想做这个题 我不会 我根本不想想
三个循环 空间时间都不好 但是思路最清晰的暴力:
class Solution {
public boolean repeatedSubstringPattern(String s){
int num = 0;
//比较成功就num++,最后等于字符串长度就比较完毕
for(int i = 0; i < s.length()/2; i++){
//控制在一半即可,只要有子串就至少是2倍
if(s.length()%(i+1) != 0) continue;
//长度不是子串倍数时,当前这轮循环不再往下执行了,直接到下一次i++
flag:{//break是只能退出当前层的循环要想指定退出那层,要用这种方式
for(int j=0; j<i+1; j++){//j控制的是每次比较的子串长度
for(int k=j; k<s.length(); k=k+i+1){
//k控制的是被分好的每个子串的第几个字符
if(s.charAt(j) != s.charAt(k)){
//只要k不对应相等了,就说明这个子串就是不行的,直接停,换下一个子串
num = 0;
break flag;
}
else num++;
}
}
}
//代表我们比较成功的次数与 s 相等
if( num== s.length()) return true;
}
return false;
}
}