信誉好的江苏网站建设,资阳公司短视频优化服务,凡科互联网,seo专员工资一般多少题目
给定一个非空的字符串#xff0c;判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母#xff0c;并且长度不超过10000。
示例 1:
输入: abab
输出: True
解释: 可由子字符串 ab 重复两次构成。示例 2:
输入: 判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母并且长度不超过10000。
示例 1:
输入: abab
输出: True
解释: 可由子字符串 ab 重复两次构成。示例 2:
输入: aba
输出: False示例 3:
输入: abcabcabcabc
输出: True
解释: 可由子字符串 abc 重复四次构成。 (或者子字符串 abcabc 重复两次构成。)思路
暴力的解法 就是一个for循环获取 子串的终止位置 然后判断子串是否能重复构成字符串又嵌套一个for循环所以是O(n^2)的时间复杂度。
有的同学可以想怎么一个for循环就可以获取子串吗 至少得一个for获取子串起始位置一个for获取子串结束位置吧。
其实我们只需要判断以第一个字母为开始的子串就可以所以一个for循环获取子串的终止位置就行了。 而且遍历的时候 都不用遍历结束只需要遍历到中间位置因为子串结束位置大于中间位置的话一定不能重复组成字符串。
主要讲一讲移动匹配 和 KMP两种方法。
移动匹配
当一个字符串sabcabc内部由重复的子串组成那么这个字符串的结构一定是这样的 也就是由前后相同的子串组成。
那么既然前面有相同的子串后面有相同的子串用 s s这样组成的字符串中后面的子串做前串前面的子串做后串就一定还能组成一个s如图 当然我们在判断 s s 拼接的字符串里是否出现一个s的的时候要刨除 s s 的首字符和尾字符这样避免在ss中搜索出原来的s我们要搜索的是中间拼接出来的s。
以上证明的充分性接下来证明必要性
如果有一个字符串s在 s s 拼接后 不算首尾字符如果能凑成s字符串说明s 一定是重复子串组成。
如图字符串s图中数字为数组下标在 s s 拼接后 不算首尾字符中间凑成s字符串。 图中因为中间拼接成了s根据红色框 可以知道 s[4] s[0] s[5] s[1] s[0] s[2], s[1] s[3] s[2] s[4] ,s[3] s[5] 以上相等关系我们串联一下
s[4] s[0] s[2]
s[5] s[1] s[3]
即s[4],s[5] s[0],s[1] s[2],s[3]
说明这个字符串是由 两个字符 s[0] 和 s[1] 重复组成的
如图
s[3] s[0]s[4] s[1] s[5] s[2]s[0] s[3]s[1] s[4]s[2] s[5]
以上相等关系串联
s[3] s[0]
s[1] s[4]
s[2] s[5]
s[0] s[1] s[2] s[3] s[4] s[5]
和以上推导过程一样最后可以推导出这个字符串是由 s[0] s[1] s[2] 重复组成。
如果是这样的呢如图 s[1] s[0]s[2] s[1] s[3] s[2]s[4] s[3]s[5] s[4]s[0] s[5]
以上相等关系串联
s[0] s[1] s[2] s[3] s[4] s[5]
最后可以推导出这个字符串是由 s[0] 重复组成。
以上 充分和必要性都证明了所以判断字符串s是否由重复子串组成只要两个s拼接在一起里面还出现一个s的话就说明是由重复子串组成。
代码如下 class Solution { public: bool repeatedSubstringPattern(string s) { string t s s; t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾 if (t.find(s) ! std::string::npos) return true; // r return false; } };
时间复杂度: O(n)
空间复杂度: O(1)不过这种解法还有一个问题就是 我们最终还是要判断 一个字符串s s是否出现过 s 的过程大家可能直接用containsfind 之类的库函数 却忽略了实现这些函数的时间复杂度暴力解法是m * n一般库函数实现为 O(m n)。
充分性证明
如果一个字符串s是由重复子串组成那么 最长相等前后缀不包含的子串一定是字符串s的最小重复子串。
证明 如果s 是有是有最小重复子串p组成。
即 s n * p
那么相同前后缀可以是这样 也可以是这样 最长的相等前后缀也就是这样 如果字符串s 是有是有最小重复子串p组成最长相等前后缀就不能更长一些 例如这样 如果这样的话因为前后缀要相同所以 p2 p1p3 p2如图 p2 p1p3 p2 即 p1 p2 p3
说明 p p1 * 3。
这样p 就不是最小重复子串了不符合我们定义的条件。
所以如果这个字符串s是由重复子串组成那么最长相等前后缀不包含的子串是字符串s的最小重复子串。 必要性证明
以上是充分性证明以下是必要性证明
如果 最长相等前后缀不包含的子串是字符串s的最小重复子串 那么字符串s一定由重复子串组成吗
最长相等前后缀不包含的子串已经是字符串s的最小重复子串那么字符串s一定由重复子串组成这个不需要证明了。
关键是要要证明最长相等前后缀不包含的子串什么时候才是字符串s的最小重复子串呢。
情况一 最长相等前后缀不包含的子串的长度 比 字符串s的一半的长度还大那一定不是字符串s的重复子串 情况二最长相等前后缀不包含的子串的长度 可以被 字符串s的长度整除如图 步骤一因为 这是相等的前缀和后缀t[0] 与 k[0]相同 t[1] 与 k[1]相同所以 s[0] 一定和 s[2]相同s[1] 一定和 s[3]相同即s[0]s[1]与s[2]s[3]相同 。
步骤二 因为在同一个字符串位置所以 t[2] 与 k[0]相同t[3] 与 k[1]相同。
步骤三 因为 这是相等的前缀和后缀t[2] 与 k[2]相同 t[3]与k[3] 相同所以s[2]一定和s[4]相同s[3]一定和s[5]相同即s[2]s[3] 与 s[4]s[5]相同。
步骤四循环往复。
所以字符串ss[0]s[1]与s[2]s[3]相同 s[2]s[3] 与 s[4]s[5]相同s[4]s[5] 与 s[6]s[7] 相同。
可以推出在由重复子串组成的字符串中最长相等前后缀不包含的子串就是最小重复子串。
即 s[0]s[1] 是最小重复子串
以上推导中你怎么知道 s[0] 和 s[1] 就不相同呢 s[0] 为什么就不能使最小重复子串。
如果 s[0] 和 s[1] 也相同同时 s[0]s[1]与s[2]s[3]相同s[2]s[3] 与 s[4]s[5]相同s[4]s[5] 与 s[6]s[7] 相同那么这个字符串就是有一个字符构成的字符串。
那么它的最长相同前后缀就不是上图中的前后缀而是这样的的前后缀 情况三最长相等前后缀不包含的子串的长度 不被 字符串s的长度整除得情况如图 步骤一因为 这是相等的前缀和后缀t[0] 与 k[0]相同 t[1] 与 k[1]相同t[2] 与 k[2]相同。
所以 s[0] 与 s[3]相同s[1] 与 s[4]相同s[2] 与s[5]即s[0]s[1]与s[2]s[3]相同 。
步骤二 因为在同一个字符串位置所以 t[3] 与 k[0]相同t[4] 与 k[1]相同。
步骤三 因为 这是相等的前缀和后缀t[3] 与 k[3]相同 t[4]与k[5] 相同所以s[3]一定和s[6]相同s[4]一定和s[7]相同即s[3]s[4] 与 s[6]s[7]相同。
以上推导可以得出 s[0],s[1],s[2] 与 s[3],s[4],s[5] 相同s[3]s[4] 与 s[6]s[7]相同。
那么 最长相等前后缀不包含的子串的长度 不被 字符串s的长度整除 就不是s的重复子串
充分条件如果字符串s是由重复子串组成那么 最长相等前后缀不包含的子串 一定是 s的最小重复子串。
必要条件如果字符串s的最长相等前后缀不包含的子串 是 s最小重复子串那么 s是由重复子串组成。
在必要条件这个是 显而易见的都已经假设 最长相等前后缀不包含的子串 是 s的最小重复子串了那s必然是重复子串。
关键是需要证明 字符串s的最长相等前后缀不包含的子串 什么时候才是 s最小重复子串。
同上我们证明了当 最长相等前后缀不包含的子串的长度 可以被 字符串s的长度整除那么不包含的子串 就是s的最小重复子串。
class Solution { public boolean repeatedSubstringPattern(String s) { if (s.equals(“”)) return false; 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;
}}