我有一个字符S
,其中包含a
和b
&#39}。执行以下操作一次。目的是获得按字典顺序排列的最小字符串。
操作:正好反转S
e.g。
S = abab
然后Output = aabb
(字符串ba
的反向S
)S = abba
然后Output = aabb
(字符串bba
的反向S
)我的方法
案例1:如果输入字符串的所有字符都相同,那么输出将是字符串本身。
案例2 如果S
的格式为aaaaaaa....bbbbbb....
,那么答案将为S
本身。
否则:在b
中找到S
的第一次出现,说位置为i。字符串S
看起来像
aa...bbb...aaaa...bbbb....aaaa....bbbb....aaaaa...
|
i
为了获得按字典顺序排列的最小字符串,将被反转的子字符串从索引i开始。请参阅下文,了解可能的结尾j。
aa...bbb...aaaa...bbbb....aaaa....bbbb....aaaaa...
| | | |
i j j j
为每个j反转子串S[i:j]
并找到最小的字符串。
算法的复杂性为O(|S|*|S|)
,其中|S|
是字符串的长度。
有没有更好的方法来解决这个问题?可能是O(|S|)
解决方案。
我在想,如果我们能够在线性时间内选择正确的j
,那么我们就完成了。我们将选择a
的数量最大的j。如果有一个最大值,那么我们就解决了问题但是如果不是这样的话怎么办?我已经尝试了很多。请帮忙。
答案 0 :(得分:1)
所以,我想出了一个算法,它似乎比O(| S | ^ 2)更有效,但我不太确定它的复杂性。这是一个粗略的大纲:
a's
的条带,存储在变量start
。a's
。index
,请转到10。b's
,请转到10。index
(不包括前导a's
)的[第一]组的长度最小。a's
,请转到10。index
和a's
的[second / third / ...]组。b's
,以及最多start
的反转组以及剩余的组。 由于任何正在反转的子字符串都以index
开头并以b
结尾,因此没有两个假设的反转是回文,因此两次反转不会产生相同的输出,从而保证有一个独特的最优解决方案,算法将终止。
我的直觉说这种方法可能是O(log(| S |)* | S |),但我不太确定。下面提供了Python中的一个示例实现(虽然不是很好的实现)。
a
答案 1 :(得分:1)
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);
&#13;
(每当找到零时,可以通过比较reverse
和value
来简化代码,而不仅仅是在遇到最大长零序列的末尾时。(
您可以创建一个只迭代输入一次的算法,并且可以通过跟踪两个值来处理未知长度的传入流:整个字符串的值被解释为反向(lsb到msb)二进制数,以及一个部分反转的字符串的值。只要反向值低于存储的最佳终点的值,就会找到更好的终点。
以此字符串为例:
aaabbaabaaabbabaaabaaab
或者,为了简单起见用零和1写的:
00011001000110100010001
我们遍历前导零,直到找到第一个:
0001
^
这是我们想要逆转的序列的开始。我们将开始将零和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
此时我们找到了一个,并且零序列结束了。它包含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个零的序列,因此我们将当前的反向值与终点值进行比较,并发现存储的端点值较低:
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};
}