假设您有两个长度为100,000的字符串,其中包含零和一。您可以通过大约10 ^ 10个运算来计算其edit distance。
如果每个字符串只有100个,其余为零,那么我可以使用100个整数表示每个字符串,表示它们在哪里。
有没有更快的算法可以使用以下方式计算编辑距离 这种稀疏的表示?更好的算法是也使用100 ^ 2空间而不是10 ^ 10空间。
要进行测试,请考虑这两个字符串,每个字符串十个。整数表明每个字符串在哪里。
[9959, 10271, 12571, 21699, 29220, 39972, 70600, 72783, 81449, 83262]
[9958, 10270, 12570, 29221, 34480, 37952, 39973, 83263, 88129, 94336]
用算法的术语来说,如果我们有两个长度分别为n
的{{1}}稀疏二进制字符串,那么是否存在k
时间编辑距离算法?
答案 0 :(得分:9)
当然!有太多0的可能操作很少。我的意思是,答案最多为200。
看看
10001010000000001
vs ||||||
10111010100000010
用管道查看所有零。您最终删除的那一个有关系吗?不。那是关键。
让我们考虑正常的n * m解决方案:
dp(int i, int j) {
// memo & base case
if( str1[i-1] == str1[j-1] ) {
return dp(i-1, j-1);
}
return 1 + min( dp(i-1, j), dp(i-1, j-1), dp(i, j-1) );
}
如果几乎每个字符都为0,那么最多的时间会花在什么地方?
if( str1[i-1] == str1[j-1] ) { // They will be equal so many times, (99900)^2 times!
return dp(i-1, j-1);
}
我可以想象一下,在数以万计的递归中滴滴答答。您实际需要的逻辑只是200个关键点。您可以忽略其余部分。一个简单的修改就是
if( str1[i-1] == str1[j-1] ) {
if( str1[i-1] == 1 )
return dp(i-1, j-1); // Already hit a critical point
// rightmost location of a 1 in str1 or str2, that is <= i-1
best = binarySearch(CriticalPoints, i-1);
return dp(best + 1, best + 1); // Use that critical point
// Important! best+1 because we still want to compute the answer at best
// Without it, we would skip over in a case where str1[best] is 1, and str2[best] is 0.
}
CriticalPoints是包含str1或str2中每1的索引的数组。在二进制搜索之前,请确保已对它进行了排序。请记住那些gochya的。我的逻辑是:好的,我需要确保在索引best
本身上计算答案,所以我们以best + 1
作为参数。但是,如果best == i - 1
,我们将陷入循环。我将通过快速str1[i-1] == 1
检查来解决这一问题。做完了,。
您可以通过以下方法快速检查正确性:注意在最坏的情况下,您会击中构成临界点的i *和j的所有200 * 100000组合,并且当这些临界点调用min(a, b, c)
时,它只会使三个递归函数调用。如果这些功能中的任何一个是关键点,那么它就是我们已经计算的200 * 100000的一部分,我们可以忽略它。如果不是,则在O(log(200))中它会落入另一个关键点的单个调用中(现在,这是我们已经知道的200 * 100000的一部分,我们知道)。因此,每个关键点花费的时间最差3*log(200)
,不包括对其他关键点的呼叫。同样,第一个函数调用将在log(200)
时间内陷入临界点。因此,我们的上限为O(200 * 100000 * 3 * log(200)+ log(200))。
此外,请确保您的备忘录表是哈希表,而不是数组。 10 ^ 10的内存将不适合您的计算机。
您知道答案最多为200,因此请防止自己进行过多的深度运算。
dp(int i, int j) { // O(100000 * 205), sounds good to me.
if( abs(i - j) > 205 )
return 205; // The answer in this case is at least 205, so it's irrelevant to calculating the answer because when min is called, it wont be smallest.
// memo & base case
if( str1[i-1] == str1[j-1] ) {
return dp(i-1, j-1);
}
return 1 + min( dp(i-1, j), dp(i-1, j-1), dp(i, j-1) );
}
这是一个非常简单的方法,但我将其留给解决方案二,因为该解决方案似乎是凭空提出的,而不是分析问题并弄清慢零件的位置以及如何对其进行优化。不过,请保留在工具箱中,因为您应该对此解决方案进行编码。
就像解决方案2一样,我们可以这样做:
dp(int i, int j, int threshold = 205) {
if( threshold == 0 )
return 205;
// memo & base case
if( str1[i-1] == str1[j-1] ) {
return dp(i-1, j-1);
}
return 1 + min( dp(i-1, j, threshold - 1), dp(i-1, j-1, threshold - 1), dp(i, j-1, threshold - 1) );
}
您可能会担心dp(i-1,j-1)滴落,但是阈值使i和j靠得很近,因此它计算了解决方案2的子集。这是因为每次i和j时,阈值都会递减。 j分开得更远。 dp(i-1, j-1, threshold)
将使其与解决方案2相同(因此,该解决方案要快一些)。
这些解决方案将很快为您提供答案,但是,如果您还想要优化空间的解决方案,则可以使用哈希图将str1[i]
替换为(i in Str1CriticalPoints) ? 1 : 0
。这将提供最终的解决方案,该解决方案仍然非常快(尽管速度会慢10倍),而且避免将长字符串保留在内存中(以至于它可以在Arduino上运行)。我认为这不是必需的。
请注意,原始解决方案不使用10 ^ 10的空间。您提到“更好,少于10 ^ 10的空间”,这意味着10 ^ 10的空间是可以接受的。不幸的是,即使有足够的RAM,迭代该空间仍需要10 ^ 10的时间,这绝对是不可接受的。我的解决方案都不使用10 ^ 10的空间:仅2 * 10 ^ 5来容纳字符串-如上所述,可以避免这种情况。 10 ^ 10字节,相当于10 GB。
编辑:如maniek所言,您只需要检查abs(i - j) > 105
,因为将i
和j
等同的其余100次插入将使操作数超过200。