TL;DR:这是一种仅对字符串进行一次迭代的算法(对于有限的字符串长度,具有 O(|S|) 的复杂性)。我在下面解释它的例子有点啰嗦,但算法真的很简单:
- 遍历字符串,并更新其解释为反向(lsb-to-msb)二进制数的值。
- 如果您找到比当前最大值更长的零序列中的最后一个零,则存储当前位置和当前反向值。从那时起,还要更新此值,将字符串的其余部分解释为正向 (msb-to-lsb) 二进制数。
- 如果找到与当前最大值一样长的零序列中的最后一个零,则将当前反向值与存储端点的当前值进行比较;如果它更小,则将端点替换为当前位置。
因此,您基本上是在比较字符串的值(如果它被反转到当前点)与字符串的值(如果它只反转到(到目前为止)最佳点),并更新这个最佳值即时点。
这是一个快速的代码示例;毫无疑问,它可以更优雅地编码:
function reverseSubsequence(str) {
var reverse = 0, max = 0, first, last, value, len = 0, unit = 1;
for (var pos = 0; pos < str.length; pos++) {
var digit = str.charCodeAt(pos) - 97; // read next digit
if (digit == 0) {
if (first == undefined) continue; // skip leading zeros
if (++len > max || len == max && reverse < value) { // better endpoint found
max = len;
last = pos;
value = reverse;
}
} else {
if (first == undefined) first = pos; // end of leading zeros
len = 0;
}
reverse += unit * digit; // update reverse value
unit <<= 1;
value = value * 2 + digit; // update endpoint value
}
return {from: first || 0, to: last || 0};
}
var result = reverseSubsequence("aaabbaabaaabbabaaabaaab");
document.write(result.from + "→" + result.to);
(代码可以通过比较 reverse 和 value 来简化,只要找到一个零,而不仅仅是遇到一个最大长度的零序列的结尾。)
您可以创建一个只对输入进行一次迭代的算法,并且可以通过跟踪两个值来处理未知长度的传入流:整个字符串的值被解释为反向(lsb-to-msb)二进制数,并将字符串的一部分取反。每当反向值低于存储的最佳端点的值时,就找到了更好的端点。
以这个字符串为例:
aaabbaabaaabbabaaabaaab
或者,为了简单起见,用零和一写成:
00011001000110100010001
我们遍历前导零,直到找到第一个:
0001
^
这是我们想要反转的序列的开始。我们将开始将 0 和 1 的流解释为反转 (lsb-to-msb) 二进制数,并在每一步之后更新这个数字:
reverse = 1, unit = 1
然后在每一步,我们将单位加倍并更新倒数:
0001 reverse = 1
00011 unit = 2; reverse = 1 + 1 * 2 = 3
000110 unit = 4; reverse = 3 + 0 * 4 = 3
0001100 unit = 8; reverse = 3 + 0 * 8 = 3
此时我们找到了一个 1,0 的序列就结束了。它包含 2 个零,这是当前的最大值,因此我们将当前位置存储为可能的端点,同时存储当前的反向值:
endpoint = {position = 6, value = 3}
然后我们继续迭代字符串,但在每一步,我们都会更新可能端点的值,但现在是一个普通的(msb-to-lsb)二进制数:
00011001 unit = 16; reverse = 3 + 1 * 16 = 19
endpoint.value *= 2 + 1 = 7
000110010 unit = 32; reverse = 19 + 0 * 32 = 19
endpoint.value *= 2 + 0 = 14
0001100100 unit = 64; reverse = 19 + 0 * 64 = 19
endpoint.value *= 2 + 0 = 28
00011001000 unit = 128; reverse = 19 + 0 * 128 = 19
endpoint.value *= 2 + 0 = 56
此时我们发现我们有一个 3 个零的序列,比当前最大的 2 长,所以我们把目前为止的端点扔掉,用当前位置和反向值替换它:
endpoint = {position = 10, value = 19}
然后我们继续遍历字符串:
000110010001 unit = 256; reverse = 19 + 1 * 256 = 275
endpoint.value *= 2 + 1 = 39
0001100100011 unit = 512; reverse = 275 + 1 * 512 = 778
endpoint.value *= 2 + 1 = 79
00011001000110 unit = 1024; reverse = 778 + 0 * 1024 = 778
endpoint.value *= 2 + 0 = 158
000110010001101 unit = 2048; reverse = 778 + 1 * 2048 = 2826
endpoint.value *= 2 + 1 = 317
0001100100011010 unit = 4096; reverse = 2826 + 0 * 4096 = 2826
endpoint.value *= 2 + 0 = 634
00011001000110100 unit = 8192; reverse = 2826 + 0 * 8192 = 2826
endpoint.value *= 2 + 0 = 1268
000110010001101000 unit = 16384; reverse = 2826 + 0 * 16384 = 2826
endpoint.value *= 2 + 0 = 2536
这里我们发现我们还有另一个3个0的序列,所以我们将当前的反向值与端点的值进行比较,发现存储的端点有一个较低的值:
endpoint.value = 2536 < reverse = 2826
所以我们将端点设置为位置 10,然后继续迭代字符串:
0001100100011010001 unit = 32768; reverse = 2826 + 1 * 32768 = 35594
endpoint.value *= 2 + 1 = 5073
00011001000110100010 unit = 65536; reverse = 35594 + 0 * 65536 = 35594
endpoint.value *= 2 + 0 = 10146
000110010001101000100 unit = 131072; reverse = 35594 + 0 * 131072 = 35594
endpoint.value *= 2 + 0 = 20292
0001100100011010001000 unit = 262144; reverse = 35594 + 0 * 262144 = 35594
endpoint.value *= 2 + 0 = 40584
我们找到另一个 3 个零的序列,所以我们将这个位置与存储的端点进行比较:
endpoint.value = 40584 > reverse = 35594
我们发现它有一个较小的值,所以我们用当前位置替换可能的端点:
endpoint = {position = 21, value = 35594}
然后我们遍历最后一个数字:
00011001000110100010001 unit = 524288; reverse = 35594 + 1 * 524288 = 559882
endpoint.value *= 2 + 1 = 71189
所以最后我们发现位置 21 给了我们最低的值,所以它是最优解:
00011001000110100010001 -> 00000010001011000100111
^ ^
start = 3 end = 21
这是一个使用 bool 向量而不是整数的 C++ 版本。它可以解析超过 64 个字符的字符串,但复杂度可能是二次方的。
#include <vector>
struct range {unsigned int first; unsigned int last;};
range lexiLeastRev(std::string const &str) {
unsigned int len = str.length(), first = 0, last = 0, run = 0, max_run = 0;
std::vector<bool> forward(0), reverse(0);
bool leading_zeros = true;
for (unsigned int pos = 0; pos < len; pos++) {
bool digit = str[pos] - 'a';
if (!digit) {
if (leading_zeros) continue;
if (++run > max_run || run == max_run && reverse < forward) {
max_run = run;
last = pos;
forward = reverse;
}
}
else {
if (leading_zeros) {
leading_zeros = false;
first = pos;
}
run = 0;
}
forward.push_back(digit);
reverse.insert(reverse.begin(), digit);
}
return range {first, last};
}