解题思路
如果您的字符串 S 包含一个重复的子字符串,那么这意味着您可以多次 “移位和换行”`您的字符串,并使其与原始字符串匹配。
例如:abcabc
移位一次:cabcab
移位两次:bcabca
移位三次:abcabc
现在字符串和原字符串匹配了,所以可以得出结论存在重复的子串。
基于这个思想,可以每次移动k个字符,直到匹配移动 length - 1 次。但是这样对于重复字符串很长的字符串,效率会非常低。在 LeetCode 中执行时间超时了。
为了避免这种无用的环绕,可以创建一个新的字符串 str,它等于原来的字符串 S 再加上 S 自身,这样其实就包含了所有移动的字符串。
比如字符串:S = acd,那么 str = S + S = acdacd
acd 移动的可能:dac、cda。其实都包含在了 str 中了。就像一个滑动窗口
一开始 acd (acd) ,移动一次 ac(dac)d,移动两次 a(cda)cd。循环结束
所以可以直接判断 str 中去除首尾元素之后,是否包含自身元素。如果包含。则表明存在重复子串。
解释:
1、假设字符串s是由s1+s2组成的,s+s后,str就变成了s1+s2+s1+s2,去掉首尾,破环了首尾的s1和s2,变成了s3+s2+s1+s4,此时str中间就是s2+s1,如果s是循环字串,也就是s1=s2,所以str中间的s2+s1就和原字符串相等。如果s不是循环字串,s1!=s2,那么s1+s2是不等于s2+s1的,也就是str中间不包含s。
@Cranky NashkCk 建议把s1=s2改为s1=N*s2, N是自然数 , 不影响你的证明, 但是更严谨。比如abcabcabc
的情况。
2、记录一下自己的理解:如果S不包含重复的子字符串,则S本身就是所谓的“重复子字符串”(这里方便自己理解,视为重复一次,不深究= =),str = S+S,说明S是str的重复子字符串,刨去str首尾两个字符之后(相当于分别破坏前一个S头部和后一个S尾部),不能满足str包含S。
3、假设判断字符串s(n个字符构成)是不是由重复子串构成的,我们先将两个字串拼成一个大的字符串:S = s + s(S 有2n个字符);字符串拼接的最底层思想应该是使用字符串移位来判断字符串是否由重复子串(循环节)构成,因为S这个大的字符串 其实包含了字符串s的所有移位字符串。令字符串字符索引从0开始,[m,n]表示S中索引为m到索引为n的这一段字符,索引为闭区间,则[0,n-1]即S的前一半字符表示原始字符串s;[1,n]表示s右移1位的状态;[2,n+1]表示s右移2位的状态;······;[n,2n-1]即S的后半拉字符串可以理解为s移n位的状态,此时s已经移回了原始的状态了;
此外我们不难知道如果一个没有循环节的字符串在移位时必须要右移n次他才能移回它的原始状态,而有循环节的字符串最多只需要n/2次(只有两个循环节);所以当S砍去首尾字符时,对于没有循环节的字符串s,相当于砍去了[0,n-1]的原始状态和[n,2n-1]这个移位n次的又回归原始的状态,我们在[1,2n-2]范围内只能找到s移位1,2,···,n-1位时的状态,所以在[1,2n-2]内是不存在无循环节的s的;但是对于有循环节的s来说,n/2 <= n-1,所以一定存在至少一个移位状态为s,即最少存在一个s(其实对于有循环节的s来说,不考虑移位状态我们也能明白[1,2n-2]内一定至少有一个s,例如对于有两个循环节的s:组成S的前一个s的后一半字符 + 组成S的后一个s的前一半的字符 == s;三个循环节的s更不用说了);
链接:力扣