Leetcode上两道很有意思的字符串题(都可以利用重复拼接自身快速解答)
今天做了Leetcode上一道简单题,一开始我想用两个指针一次循环的方法来做,结果怎么都通过不了,无奈看了答案,答案的方法非常巧妙,通过拼接自身字符串发现潜在的特性。
这种题目我已经发现了两道,一起来看看这两道题。
459.重复的子字符串
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。
示例 2:
输入: "aba"
输出: False
示例 3:
输入: "abcabcabcabc"
输出: True
解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)
这道题的第一想法就是暴力搜,遍历第1到第n/2个子字符串,设从0开始,长度为x,对每个子字符串都遍历剩下的(n/x)-1个字符串,按顺序剩下的每一个字符s[i]都应该和s[i-x]相同,所以再循环一个总字符串的长度即可。时空复杂度较高。
除了这种暴力解法之外,Leetcode官方提供了一个非常简洁有效的解法(不是KMP,我也不会KMP) 。先给出答案的链接:
答案通过重复拼接自身构成(s,s)字符串,如果该字符串是由重复的子字符串组成的,那么去头掐尾以后,形成的新字符串(s,s)[1:-1]中一定包含原来的字符串s,读者可以自己试试,这里就不给出证明了,官方答案不仅证明了充分性也证明了必要性。也就是说,我们构造一个(s,s)字符串后,再掐头去尾得到(s,s)[1:-1],在这个字符串中找s,如果找不着,那么说明这个字符串应该不是重复子字符串得到的。代码的逻辑是,找(s,s)[1:]中的s,如果找到的是最后一个末尾的s,那么就不是重复子字符串。
贴个代码:
class Solution { public: bool repeatedSubstringPattern(string s) { return (s+s).find(s, 1) != s.size(); } };
巧合的是,我前几天也做了一题——字符串轮转,这道题是一道面试题。同样也是使用拼接自身构造了一个新的字符串再去判断。先看题目:
面试题01.09 字符串轮转
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
示例1:
输入:s1 = "waterbottle", s2 = "erbottlewat"
输出:True
示例2:
输入:s1 = "aa", s2 = "aba"
输出:False
提示:
字符串长度在[0, 100000]范围内。
说明:
你能只调用一次检查子串的方法吗?
同样的,当我们拿到这道题,第一思路就是使用一次循环区构造所有的字符串轮转后的结果S1,S2...Sn,同时比较s2是否与之相同,如果有一个相同说明s2是s1旋转得到的。但是题目要求我们只使用一次检查子串的方法。
做法很简单,如果s2是s1旋转得到的,重复拼接s1得到(s1,s1)中一定包含s2,相当于是把所有旋转的结果都放进了一个字符串中。那么代码就很简单了。
class Solution { public: bool isFlipedString(string s1, string s2) { if (s1.size() != s2.size()) return false; return (s2 + s2).rfind(s1) != -1; } };
我在这里用的是C++中rfind的方法,当然直接使用find也是可以的。
总结
两道题中的字符串,第一个是重复子字符串,第二个是旋转得到的字符串,都具备类似的性质,
当重复子字符串得到的字符串s拼接自身(s,s)后,即便掐头去尾,s也在(s,s)[1:-1]中,
当字符串s拼接自身(s,s)后,由它旋转得到的所有字符串s1,s2...Sn,都在(s,s)中,
所以两道题都利用了这个性质,写出了简洁有效的代码。