55. 右旋字符串https://kamacoder.com/problempage.php?pid=1065
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
输入示例:
2
abcdefg
输出示例:
fgabcde
第一个想法是利用多余的空间把后k个保存,然后依次从后往前遍历,但是,代码随想录给出了限制,不允许使用额外的空间。字符串本身能做的事情有遍历,字符校验,字符串追加,以及翻转。
字符串追加,先从n-k开始进行append,然后从0开始append,时间复杂度是O(n),但是,需要额外的一个n个长度的StringBuilder,所以最后的方案就是翻转。先进行整体的反转,然后进行局部的反转。
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int k = scanner.nextInt();
StringBuilder sb = new StringBuilder(scanner.next());
int n = sb.length();
reverse(sb, 0, n - 1);
reverse(sb, 0, k - 1);
reverse(sb, k, n - 1);
System.out.println(sb);
}
private static void reverse(StringBuilder sb, int left, int right) {
while (left <= right) {
char temp = sb.charAt(right);
sb.setCharAt(right--, sb.charAt(left));
sb.setCharAt(left++, temp);
}
}
}
28. 找出字符串中第一个匹配项的下标https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/
给你两个字符串 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,所以选择使用KMP。
先提出前缀和后缀两个概念,由于当全部都匹配的时候就结束了,讨论前后缀没有意义了, 所以这里的前缀后缀都是真前缀和真后缀。
我们要留意的就是在已经匹配的这一部分字符串,它的后缀,和要匹配的字符串的前缀究竟有多少匹配。所以引出了KMP的一个关键部分,next数组。
next数组在我看来,如同一个转发表,在匹配aabaaf的过程中,当某一个位置匹配失败的时候,我要回到哪一个位置进行匹配。在1错误了需要回到0比(a没有(真)后缀),在2错误了需要回到1比(aa有一个后缀a和整个字符的前缀a一样),在3错误了需要回到0比(后缀b和ab没有一个与前缀匹配),在4错误了回到1(aaba的后缀a和前缀a一样),在5错误了回到2,在6错误了回到3
当我进行遍历匹配的时候,发现某一个字符匹配失败了,然后去next数组查上一个位置的值,上一个位置的值x表示当前已经重新匹配了x个字符了,所以我重新从第x+1个开始我的下次匹配。
如何维护这个next数组呢?
next数组和匹配字符串长度一致,next[0]固定为0,后续如果next[i-1]=x,那么需要判断next[i]是否和第x个字符匹配,如果匹配next[i] = x+1,如果不匹配,也就是说在进行匹配的时候发现和第x个字符串不匹配了,正好利用next数组本身进行跳转,我去匹配next[x-1],直到我匹配到了或者到第0位了。
class Solution {
public int strStr(String haystack, String needle) {
char[] need = needle.toCharArray();
char[] hays = haystack.toCharArray();
int[] next = new int[need.length];
// 构建next
next[0] = 0;
for (int i = 1; i < next.length; i++) {
int j = next[i - 1];
// 如果和第j位置的不匹配,利用next进行跳转
while (j > 0 && need[j] != need[i]) {
j = next[j - 1];
}
// 如果最后跳转到了一个前缀位置,那么+1
if (need[j] == need[i]) {
next[i] = j + 1;
}
// 没有就是0
else {
next[i] = 0;
}
}
int index = 0;
for (int i = 0; i < hays.length; i++) {
// index作为索引记录匹配位置,如果不匹配那就一直跳转,最后跳到0或者匹配了
while (index > 0 && hays[i] != need[index]) {
index = next[index - 1];
}
// 如果跳转后匹配了,index移动
if (hays[i] == need[index]) {
index++;
}
// 如果超过长度了,说明匹配成功
if (index == need.length) {
return i - need.length + 1;
}
}
return -1;
}
}
459. 重复的子字符串https://leetcode.cn/problems/repeated-substring-pattern/description/给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba"
输出: false
示例 3:
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
除开暴力循环破解,可以考虑使用KMP。
next数组是从0到当前i这个位置的字符串的后缀和s的前缀的最大相等情况,对next的最后一个元素来说,就是最大相等前后缀。
如果s的next的最后一位元素的值为x,n-x就是循环字符串的长度。如果这个长度可以被n所整除的话,就说明是循环的。
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s == " ")
return false;
char[] charS = s.toCharArray();
int n = s.length();
int[] next = new int[n];
next[0] = 0;
for (int i = 1; i < n; i++) {
int j = next[i - 1];
while (j > 0 && charS[j] != charS[i]) {
j = next[j - 1];
}
if (charS[j] == charS[i]) {
next[i] = j + 1;
} else {
next[i] = 0;
}
}
if (next[n - 1] > 0 && n % (n - next[n - 1]) == 0) {
return true;
}
return false;
}
}