学算法,刷力扣,加油卷,进大厂!
题目描述
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba"
输出: false
示例 3:
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
提示:
- 1 <= s.length <= 104
- s 由小写英文字母组成
涉及知识点
这看着是一道非常简单的题目,当然了使用暴力解法也是可以解决的。不过,这道题目涉及了一个很重要的思想,就是KMP算法。这道题目涉及的知识点是:
- 字符串是一种特殊的数组,其可以放入Char数组中
- KMP算法
KMP算法:一种改进的字符串匹配算法,其是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。(更详细的内容可自行查阅)
而这道题目之所以说涉及了KMP算法,就是next数组的一个理解,next数组记录的就是最长相等前后缀的长度,如果next[len - 1] != -1,则说明字符串有相等的前后缀。那么,如果len % (len - (next[len - 1] +1)== 0,则说明(数组长度-最长相等前后缀的长度)正好被数组长度整除,即该字符串中有重复的子字符串。
字符数组长度为length,最长相等前后缀的长度next[len - 1] +1
那么根据题目,我们可以提炼的关键点:
- 字符串非空
- 找字符串的子串,其满足重复子串可构成原字符串
当然了,这道题目的暴力解法就是枚举的形式,毕竟简单可暴力,为何不试一下呢?
题目解答
Java题解一
思路分析
先来一个暴力解法,小试牛刀。这道题目的暴力解法就是枚举所有子串,然后然后遍历字符串s,看下是否满足题设,可通过比较s.charAt(i) 与 s.charAt(i-len)是否相等来判断。
具体思路如下:
- 定义一个字符串的长度n,方便后面用
- 开始遍历字符串了,开始假定满足条件的子串为1/2,然后是1/3等,即子串从大往小了试
- 得出子串的长度后,进行比较
- 用s.charAt(j) != s.charAt(j-len)); 第j个和下一个子串的第j个比较
class Solution {
public boolean repeatedSubstringPattern(String s) {
int n = s.length(); //字符串长度
//遍历字符串
for(int i = 2; i <= n;i++){
//长度满足是i的倍数的情况下(子串从大往小了试)
if(n%i == 0){
boolean temp = true;
int len = n/i; //子串的长度
//判断是否满足重复子串
for(int j = len;j < n;j++){
if(s.charAt(j) != s.charAt(j-len)){ //第j个和下一个子串的第j个
temp = false;
break;
}
}
if(temp) return true;
}
}
return false;
}
}
Java题解二
思路分析
这个思路是通过KMP算法的形式来解决。即数组长度减去最长相等前后缀的长度相当于第一个重复子字符串的长度,也就是一个重复周期的长度,如果这个周期可以被整除,则说明整个数组就是这个周期的循环,则说明满足题目条件。
具体思路如下:
- 定义数组长度len,方便使用
- 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
- 构造 next 数组过程,j从0开始(空格),i从2开始进行匹配:
-(1) 匹配不成功,j回到前一位置 next 数组所对应的值
-(2)匹配成功,j往后移,更新 next 数组的值 - 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
class Solution {
//KMP
public boolean repeatedSubstringPattern(String s) {
int len = s.length();
// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
// 构造 next 数组过程,j从0开始(空格),i从2开始
for (int i = 2, j = 0; i <= len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
// 匹配成功,j往后移
if (chars[i] == chars[j + 1]) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len] > 0 && len % (len - next[len]) == 0) {
return true;
}
return false;
}
}