我正在尝试解决一个算法问题,我有一个O(n²)
时间,O(n)
内存解决方案(见下文),而不是O(n)
时间和内存解决方案。
问题是计算给定字符串s的同构循环移位的数量。 循环移位是初始字符串的转换,例如if 0 <= k < n
(其中n是字符串的长度):
cyclicShift(0) = s
cyclicShift(k) = s[k-1]...s[n-1]s[0]...s[k] if k > 0
如果循环移位等于初始字符串,则表示 isomorphic 。我觉得一个字符串可以有这样的循环移位,如果它包含重复的模式,但我不能证明它。如果是这种情况,问题将变为找到这种模式,然后根据模式的长度和字符串的长度推导出同构循环移位的数量。
我当前的解决方案构造所有循环移位并将它们与初始字符串进行比较,初始字符串是在由n限定的循环中的O(n)
操作,导致O(n²)
的复杂性。这是我在Java中的代码供参考:
public int solution(String S) {
int count = 1;
int n = S.length();
// We represent the string as a LinkedList to construct the next cyclic shift
// from a given one with a O(1) time complexity
List<Character> list = new LinkedList<>();
for (int i=0 ; i<n ; i++)
list.add(S.charAt(i));
Deque<Character> tmp = new LinkedList<>(list);
for (int k=1 ; k<n ; k++) {
tmp.addFirst(tmp.removeLast());
// this test is O(n) so this solution is O(n^2)
if (tmp.equals(list))
count++;
}
return count;
}
您是否知道如何根据O(n)
要求解决此问题?答案可以是Java,Scala或伪代码。
答案 0 :(得分:3)
我认为你是完全正确的,因为同构循环移位意味着字符串由重复模式组成。
考虑原始字符串的前k个字符,根据循环移位的定义,它们等于原始字符串的第二个k字符。
现在,考虑原始字符串的第二个k字符。这些将等于原始字符串的第三个k字符,依此类推,直到您显示该字符串包含重复n / k次的k个字符的模式。
现在的问题是将字符串识别为O(n)中的重复模式。
这样做的一种方法是使用KMP failure function。失败函数告诉您在位置i匹配的字符串的最长正确前缀。如果计算字符串末尾的失败函数的值,它将告诉您一个数字T,它是与字符串后缀匹配的正确前缀的长度。
例如,考虑字符串ABCABCABC。失败功能将是:
-1 0 0 0 1 2 3 4 5 6
因此字符串末尾的值为6,这告诉我们重复模式的长度为p = n-T,在这种情况下为9-6 = 3.
一旦你有了这个最小重复模式的长度,你可以简单地尝试所有的倍数并检查它们是否划分了字符串的长度:
m=p
count=0
while(m<n)
if n%m==0: count+=1
m+=p
总的来说,这是时间和空间上的O(n)。
答案 1 :(得分:0)
我最近又遇到了类似的问题,唯一的区别是这次字符串是由重复模式构成的事实是明确指定的,而不是仅仅从规范中推导出来。
我轻松实现了一个Python函数,它可以找到字符串中最小重复模式的长度。我在这里添加它是因为如果使用像Peter de Rivaz建议的KMP要简单得多,但是他的答案也带来了一个证明,即同构循环移位的存在意味着字符串是由重复模式构成的,也是解决问题的方法一旦找到最小的模式就会出现问题。
即使它是用Python编写的,我相信这段代码也很容易理解:
def smallestPattern(s)
(count,res) = (0,1)
for i,ch in enumerate(s):
if ch != line[count]:
count = 0
res += 1
else:
count += 1
return res