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 。提示:
1 <= haystack.length, needle.length <= 104
haystack
和needle
仅由小写英文字符组成
解法一、api
用了一分钟。只是抱着试试的心情,确实第一次知道是可以放字符串的。。。
public int strStr(String haystack, String needle) {
return haystack.indexOf(needle);
}
解法二、 遍历匹配
其实类似双指针捏
class Solution {
public int strStr(String ss, String pp) {
int n = ss.length(), m = pp.length();
char[] s = ss.toCharArray(), p = pp.toCharArray();
// 枚举原串的「发起点」
for (int i = 0; i <= n - m; i++) {
// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int a = i, b = 0;
while (b < m && s[a] == p[b]) {
a++;
b++;
}
// 如果能够完全匹配,返回原串的「发起点」下标
if (b == m) return i;
}
return -1;
}
}
作者:宫水三叶
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solutions/575568/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法三、KMP算法
详情见28拓展
class Solution {
public int strStr(String haystack, String needle) {
int lenS = haystack.length(),lenT = needle.length();
int[] next = new int [lenT+1];
processNext(next,needle);
int i = 0,j = 0;
while(i != lenS && j != lenT){
if(j == -1 || haystack.charAt(i) == needle.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
}
if(j == lenT){
return i - j;
}else{
return -1;
}
}
private void processNext(int[] next,String t){
next[0] = -1;
int i = -1,j = 0;//前缀,后缀
while(j < t.length()){
if(i == -1 || t.charAt(i) == t.charAt(j)){
i++;
j++;
next[j] = i;
}else{
i = next[i];
}
}
}
}
28拓展
(学了四个小时。。)
Manacher(马拉车算法)
Manacher的作用就是在O(N)的时间复杂度下求出以每个位置为回文中心的最长回文半径。所利用的特征:2n+1预处理的奇数串,已经查询到的中心C与R(类似DP,存储子问题)回文串本身的对称性。
第三个是优化,模拟构造而非真实构造+#串的思想。
摘自以上链接二。
public class Manacher {
/**
* 对输入字符串预处理,加上辅助字符#
* @param str
* @return
*/
private static char[] preprocess(String str) {
char[] strArr = new char[str.length()*2+1];
for (int i = 0; i < strArr.length; i++) {
strArr[i] = (i&1) == 0 ? '#' : str.charAt(i/2);
}
return strArr;
}
/**
* 寻找一个字符串中最长的回文子串,返回这个回文字符串的长度
* @param str
* @return
*/
public static int maxLcpsLength(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] strArr = preprocess(str);
int[] rArr = new int[strArr.length]; // 回文半径数组
int R = -1; // 回文右边界
int C = -1; // 回文中心
int max = Integer.MIN_VALUE;
for (int i = 0; i < strArr.length; i++) {
// 确定i位置回文半径的初始值
// 当i>=R时,最短的回文半径是1,也就是字符本身;反之,最短的回文半径可能是i对应的i'的回文半径或者i到R的距离
rArr[i] = R > i ? Math.min(rArr[2*C-i], R-i) : 1;
// 尝试向外扩展
while (i + rArr[i] < strArr.length && i - rArr[i] >= 0) {
if (strArr[i+rArr[i]] == strArr[i-rArr[i]]) {
rArr[i]++;
} else {
break;
}
}
if (i + rArr[i] > R) {
R = i+rArr[i];
C = i;
}
// 更新最大回文半径的值
max = Math.max(max, rArr[i]);
}
// 这里为什么返回值是max-1,因为预处理之后的字符串和原字符串不同,
// 所以这里得到的最大回文半径其实是原字符串的最大回文子串长度加1
return max - 1;
}
public static void main(String[] args) {
String str1 = "12321";
System.out.println(maxLcpsLength(str1));
}
}
BF的模板
BF:i往前动,j也往前动,i到母串的下一格,j动到子串的首位。
public int BF(String S[], String T[]) {
int i = 0,j=0;
while(i != S.length && j != T.length){
if(S[i] == T[j]){
i++;
j++;
}else{
i = i - j + 1;
j = 0;
}
}
if(j == T.length){
return i - j;
}else{
return -1;
}
}
KMP的模板
KMP:i不动,j滑动。j到next数组所记录的位置,开始比对。
public int BMF(String s, String t) {
int lenS = s.length(),lenT = t.length();
int[] next = new int [lenT+1];
processNext(next,t);
int i = 0,j = 0;
while(i != lenS && j != lenT){
if(j == -1 || s.charAt(i) == t.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
}
if(j == lenT){
return i - j;
}else{
return -1;
}
}
private void processNext(int[] next,String t){
next[0] = -1;
int i = -1,j = 0;//前缀,后缀
while(j < t.length()){
if(i == -1 || t.charAt(i) == t.charAt(j)){
i++;
j++;
next[j] = i;
}else{
i = next[i];
}
}
}
processNext也可以写为如下。第一轮中,while跳过,s.chatAt(0) == s.charAt(1)
for (int i = 1; i < n; ++i) {
int j = fail[i - 1];
while (j != -1 && s.charAt(j + 1) != s.charAt(i)) {
j = fail[j];
}
if (s.charAt(j + 1) == s.charAt(i)) {
fail[i] = j + 1;
}
}
686. 重复叠加字符串匹配(中等)
给定两个字符串
a
和b
,寻找重复叠加字符串a
的最小次数,使得字符串b
成为叠加后的字符串a
的子串,如果不存在则返回-1
。注意:字符串
"abc"
重复叠加 0 次是""
,重复叠加 1 次是"abc"
,重复叠加 2 次是"abcabc"
。示例 1:
输入:a = "abcd", b = "cdabcdab" 输出:3 解释:a 重复叠加三遍后为 "abcdabcdabcd", 此时 b 是其子串。示例 2:
输入:a = "a", b = "aa" 输出:2示例 3:
输入:a = "a", b = "a" 输出:1示例 4:
输入:a = "abc", b = "wxyz" 输出:-1提示:
1 <= a.length <= 104
1 <= b.length <= 104
a
和b
由小写英文字母组成
解法一、先乘再匹配
n是让循环后串长度大于或者等于b长度,如果做不到,那就再添一个a然后再试一次
耗时七分钟
class Solution {
public static int repeatedStringMatch(String a, String b) {
int lenA = a.length(),lenB = b.length();
int n = lenB / lenA;
if(lenB % lenA != 0)n++;
StringBuffer sb = new StringBuffer();
for(int i = 0;i < n;i++)sb.append(a);
int res = sb.indexOf(b);
if(res!= -1)return n;
sb.append(a);
res = sb.indexOf(b);
return res != -1 ? n+1 :-1;
}
}
解法二、Rabin-Karp 算法
顺便学了下字符串哈希,本质上就是字符串预处理,其实相当于前缀和
repeatedStringMathc
an
和bn
分别是字符串a
和b
的长度。strStr(a, b)
调用了字符串匹配方法,返回字符串b
在a
中的起始位置index
。- 如果
index
为-1
,说明b
在a
中不存在,返回-1
。- 如果
a
从index
开始的剩余部分长度大于等于b
的长度,返回1
,表示b
可以在a
中找到,不需要重复。- 计算最少重复次数公式
(bn + index - an - 1) / an + 2
。该公式考虑了从index
开始未覆盖b
的部分需要多少个a
重复加上多余的部分.
strStr
初始化:
- 获取
haystack
和needle
的长度n
和m
。- 如果
needle
为空,返回0
。哈希初始化:
k1
和k2
用于生成随机的哈希模数。- 随机生成
kMod1
和kMod2
。- 计算
needle
的哈希值hashNeedle
。计算初始
haystack
的哈希值:
- 计算
haystack
前m-1
个字符的哈希值hashHaystack
。- 计算用于减去前一个字符的乘积
extra
。滑动窗口比较:
- 使用滑动窗口方式遍历
haystack
。- 计算
haystack
当前窗口的哈希值hashHaystack
。- 比较
hashHaystack
和hashNeedle
,如果相等则返回起始索引。- 如果不相等,通过减去窗口的第一个字符的影响来更新
hashHaystack
。返回结果:
- 如果找到匹配,返回起始索引;否则返回
-1
。
class Solution {
static final int kMod1 = 1000000007;
static final int kMod2 = 1337;
public int repeatedStringMatch(String a, String b) {
int an = a.length(), bn = b.length();
int index = strStr(a, b);
if (index == -1) {
return -1;
}
if (an - index >= bn) {
return 1;
}
return (bn + index - an - 1) / an + 2;
}
public int strStr(String haystack, String needle) {
int n = haystack.length(), m = needle.length();
if (m == 0) {
return 0;
}
int k1 = 1000000009;
int k2 = 1337;
Random random = new Random();
int kMod1 = random.nextInt(k1) + k1;
int kMod2 = random.nextInt(k2) + k2;
long hashNeedle = 0;
for (int i = 0; i < m; i++) {
char c = needle.charAt(i);
hashNeedle = (hashNeedle * kMod2 + c) % kMod1;
}
long hashHaystack = 0, extra = 1;
for (int i = 0; i < m - 1; i++) {
hashHaystack = (hashHaystack * kMod2 + haystack.charAt(i % n)) % kMod1;
extra = (extra * kMod2) % kMod1;
}
for (int i = m - 1; (i - m + 1) < n; i++) {
hashHaystack = (hashHaystack * kMod2 + haystack.charAt(i % n)) % kMod1;
if (hashHaystack == hashNeedle) {
return i - m + 1;
}
hashHaystack = (hashHaystack - extra * haystack.charAt((i - m + 1) % n)) % kMod1;
hashHaystack = (hashHaystack + kMod1) % kMod1;
}
return -1;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-string-match/solutions/1170235/zhong-fu-die-jia-zi-fu-chuan-pi-pei-by-l-vnye/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法三:KMP算法
同28
class Solution {
public int repeatedStringMatch(String a, String b) {
int an = a.length(), bn = b.length();
int index = strStr(a, b);
if (index == -1) {
return -1;
}
if (an - index >= bn) {
return 1;
}
return (bn + index - an - 1) / an + 2;
}
public int strStr(String haystack, String needle) {
int n = haystack.length(), m = needle.length();
if (m == 0) {
return 0;
}
int[] pi = new int[m];
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
j = pi[j - 1];
}
if (needle.charAt(i) == needle.charAt(j)) {
j++;
}
pi[i] = j;
}
for (int i = 0, j = 0; i - j < n; i++) { // b 开始匹配的位置是否超过第一个叠加的 a
while (j > 0 && haystack.charAt(i % n) != needle.charAt(j)) { // haystack 是循环叠加的字符串,所以取 i % n
j = pi[j - 1];
}
if (haystack.charAt(i % n) == needle.charAt(j)) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-string-match/solutions/1170235/zhong-fu-die-jia-zi-fu-chuan-pi-pei-by-l-vnye/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
459. 重复的子字符串(简单)
给定一个非空的字符串
s
,检查是否可以通过由它的一个子串重复多次构成。示例 1:
输入: s = "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2:
输入: s = "aba" 输出: false示例 3:
输入: s = "abcabcabcabc" 输出: true 解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)提示:
1 <= s.length <= 104
s
由小写英文字母组成
解法一、暴力
只要母串长度是子串长度的整数倍,就先造子串再比对
class Solution {
public boolean repeatedSubstringPattern(String s) {
int len = s.length();
for(int i = 1;i < len;i++){
if(len % i == 0){
String a = s.substring(0,i);
StringBuffer sb = new StringBuffer();
while(sb.length() < s.length()) sb.append(a);
if(s.equals(sb.toString()))return true;
}
}
return false;
}
}
解法二、枚举
和上面类似,但改成不造子串,而是轮番和新子串比对
class Solution {
public boolean repeatedSubstringPattern(String s) {
int n = s.length();
for (int i = 1; i * 2 <= n; ++i) {
if (n % i == 0) {
boolean match = true;
for (int j = i; j < n; ++j) {
if (s.charAt(j) != s.charAt(j - i)) {
match = false;
break;
}
}
if (match) {
return true;
}
}
}
return false;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-substring-pattern/solutions/386481/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法三、匹配
对于s和最大重复组a,至少可以写成s=a+a的形式。那么s+s = a+a+a+a,移除第一个和最后一个字符,则会变成b+a+a+c。只要不是在s.length的位置,那么就符合题意。
class Solution {
public boolean repeatedSubstringPattern(String s) {
return (s + s).indexOf(s, 1) != s.length();
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-substring-pattern/solutions/386481/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法四、KMP
本质上是替代了解法三的indexOf。注意在本次kmp里,最后一次循环的条件是i=1,i<m-1,这里的起始1和m-1其实就是挖去最后一个字母的意思。
class Solution {
public boolean repeatedSubstringPattern(String s) {
return kmp(s + s, s);
}
public boolean kmp(String query, String pattern) {
int n = query.length();
int m = pattern.length();
int[] fail = new int[m];
Arrays.fill(fail, -1);
for (int i = 1; i < m; ++i) {
int j = fail[i - 1];
while (j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)) {
j = fail[j];
}
if (pattern.charAt(j + 1) == pattern.charAt(i)) {
fail[i] = j + 1;
}
}
int match = -1;
for (int i = 1; i < n - 1; ++i) {
while (match != -1 && pattern.charAt(match + 1) != query.charAt(i)) {
match = fail[match];
}
if (pattern.charAt(match + 1) == query.charAt(i)) {
++match;
if (match == m - 1) {
return true;
}
}
}
return false;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-substring-pattern/solutions/386481/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法五、KMP优化
基本假设: 假设字符串s由多个重复子串构成,这个重复子串的长度是x,s的长度是n。 所以s由n/x个重复子串组成。
最长相同前后缀: s的最长相同前后缀不能是s本身,所以它的长度必须小于n。 假设最长相同前后缀的长度是m*x,其中m是某个整数。
关键关系: n - m = 1 这意味着最长相同前后缀的长度比s的长度恰好少一个重复单位。
判断条件: 如果n*x % (n-m)*x = 0,则s由重复子串构成。 简化后,就是n % (n-m) = 0。
KMP算法中的next数组: next[len-1]表示s的最长相同前后缀的长度减1。 所以,最长相同前后缀的长度 = next[len-1] + 1。
最终判断条件: len % (len - (next[len-1] + 1)) == 0 这等价于 n % (n-m) = 0。
解释:
- len 是字符串的总长度,相当于n。
- (next[len-1] + 1) 是最长相同前后缀的长度,相当于m*x。
- (len - (next[len-1] + 1)) 就是 (n-m)*x,即一个完整的重复单位的长度。
结论: 如果字符串的长度能被一个完整的重复单位的长度整除,那么这个字符串就是由重复子串构成的。
例:
如对于abcabcabcabc,重复子串长度是3,s长度是12,由四个子串组成。最长重复前缀=最长重复后缀=abcabcabc,m=3。也就是[abcabcabc]abc = abc[abcabcabc](最长重复前后缀不能是它自身)
也就是说,字符串长度len(12) % 最小重复单位(3,这是由总长度-最长重复串=12-9) = 0算出的。而next中正好是臂长
class Solution {
public boolean repeatedSubstringPattern(String s) {
return kmp(s);
}
public boolean kmp(String pattern) {
int n = pattern.length();
int[] fail = new int[n];
Arrays.fill(fail, -1);
for (int i = 1; i < n; ++i) {
int j = fail[i - 1];
while (j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)) {
j = fail[j];
}
if (pattern.charAt(j + 1) == pattern.charAt(i)) {
fail[i] = j + 1;
}
}
return fail[n - 1] != -1 && n % (n - fail[n - 1] - 1) == 0;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-substring-pattern/solutions/386481/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
214. 最短回文串(困难)
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为
回文串
。找到并返回可以用这种方式转换的最短回文串。示例 1:
输入:s = "aacecaaa" 输出:"aaacecaaa"示例 2:
输入:s = "abcd" 输出:"dcbabcd"提示:
0 <= s.length <= 5 * 10^4
s
仅由小写英文字母组成
解法一、Rabin-Karp字符串哈希
举例,对于[aecbcea]de,本质上是去掉eac、把b右面的de翻转过去。找到最小的填补字符串,也就是找到最大的、从字符串0位开始的回文字符串。
对于正着的字符串,f[i] = (f[i-1] * base + s.charAt[i])%mod
对于反着的字符串,f[i] = (f[i-1] + mul * s.chatAt[i]) %mod
为了防止溢出,记得把乘数置long,最后转回去。mul起到一个平方的作用,其实用(int)Math.pow(base,i)也是可以的,但逐次累乘记录也防止api内部每次都乘一遍
best在上面起到那个de中d的下标的作用,即回文字符串的最小右边界(不包括)
class Solution {
public static String shortestPalindrome(String s) {
int base = 131,mod = 1000000007;
int n = s.length();
if(n == 0)return "";
if(n == 1)return s;
int left = 0,right = 0,mul = 1;
int best = -1;
for(int i = 0;i < n;i++){
left = (int)(((long)left * base + s.charAt(i))%mod);
right = (int)((right + (long)mul * s.charAt(i))%mod);
if(left==right) best = i;
mul = (int)((long)mul * base %mod);
}
String add = (best == n-1) ? "" : s.substring(best+1);
StringBuffer ans = new StringBuffer(add).reverse();
ans.append(s);
return ans.toString();
}
}
解法二、KMP
换了个写法就没看懂。。下次再看
class Solution {
public static String shortestPalindrome(String s) {
int n = s.length();
int[] fail = new int[n];
Arrays.fill(fail,-1);
for(int i = 1;i<n;++i){
int j = fail[i-1];
while(j != -1 && s.charAt(j+1) != s.charAt(i)){
j = fail[j];
}
if(s.charAt(j+1) == s.charAt(i)){
fail[i] = j+1;
}
}
int best = -1;
for(int i = n-1;i >= 0;i--){
while(best != -1 && s.charAt(best + 1) != s.charAt(i)){
best = fail[best];
}
if(s.charAt(best+1) == s.charAt(i)){
best++;
}
}
String add = best==n - 1? "" : s.substring(best+1);
StringBuffer ans = new StringBuffer(add).reverse();
ans.append(s);
return ans.toString();
}
}
解法三、马拉车
就不给出具体代码了因为没写出来···但是有些感触(模拟构造而非真实构造,只遍历前一半)
碎碎念
- 马拉车算法:产生数组,记录以该下标位为中心回文串的臂长。
- Rabin-Karp:俩字符串是否匹配。由base的增速和方向,可以产生变式,处理固定首位不定长度的字符串是否为回文。
- KMP:一个字符串是否是另一个字符串的子串。变式有点难,暂时没看懂。。今天四道题写了七小时有点脑子不够用了