TL; DR: Oto algorytm, który tylko iteruje ciąg raz (z O (| S |) -ish złożoność ograniczonych długościach smyczkowych). Przykład z którym wyjaśnię to poniżej jest nieco rozwlekły, ale algorytm jest naprawdę bardzo proste:
- iteracyjne nad ciąg, i aktualizuje jego wartość interpretowana jako odwrotność (LSB-to-MSB) Liczba binarna.
- Jeśli znajdziesz ostatnie zero w sekwencji zer dłuższej niż bieżące maksimum, zapisz bieżącą pozycję i bieżącą wartość odwrotną. Od tego momentu zaktualizuj tę wartość, interpretując resztę ciągu jako liczbę binarną do przodu (msb-to-lsb).
- Jeśli znajdziesz ostatnie zero w sekwencji zer, która jest tak długa, jak bieżące maksimum, porównaj bieżącą wartość odwrotną z bieżącą wartością zapisanego punktu końcowego; jeśli jest mniejszy, zastąp punkt końcowy aktualną pozycją.
Więc w zasadzie porównywania wartości ciąg jakby były odwrócone do aktualnego punktu, o wartości łańcucha gdyby tylko odwrócić się do (tak daleko) optymalnym punkcie i aktualizowanie tego optymalnego punktu w locie.
Oto przykład szybkiego kodu; może to niewątpliwie być kodowane bardziej eleganckie:
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);
(kod może być uproszczona przez porównanie reverse
i value
gdy zera występuje, a nie tylko wtedy, gdy koniec maksymalnie długa sekwencja zera napotkano.)
można stworzyć algorytm, który tylko iteracje nad wejściem raz, a może przetwarzać przychodzącego strumienia o nieznanej długości, poprzez śledzenie dwóch wartości: wartość całego łańcucha interpretowany jako odwrotność (LSB -to-msb) liczba binarna i wartość ciągu z jedną częścią odwróconą. Za każdym razem, gdy wartość odwrotna spada poniżej wartości zapisanego najlepszego punktu końcowego, znaleziono lepszy punkt końcowy.
Rozważmy następujący ciąg jako przykład:
aaabbaabaaabbabaaabaaab
lub, napisany z zer i jedynek dla uproszczenia:
00011001000110100010001
Mamy iteracyjne nad zerem na początku, dopóki nie znajdziemy pierwszy:
0001
^
To jest początek sekwencji, którą chcemy cofnąć. Zaczniemy interpretacji strumień zer i jedynek jako odwrócone (LSB do MSB) liczby binarnej i aktualizować ten numer po każdym kroku:
reverse = 1, unit = 1
Następnie na każdym kroku, podwoimy urządzenie i aktualizować odwrotnie numer:
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
W tym miejscu znajdujemy jeden, a ciąg zera dobiega końca.Zawiera 2 zer, która obecnie jest maksymalna, więc zapisać bieżącą pozycję jako możliwego punktu końcowego, a także przechowywać aktualną wartość wstecznych:
endpoint = {position = 6, value = 3}
Potem idziemy na iteracji nad ciągiem, ale na każdy krok, musimy zaktualizować wartość możliwego punktu końcowego, ale teraz jako normalny (MSB do LSB) liczbę binarną:
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
w tym momencie okazuje się, że mamy sekwencję 3 zer, który jest dłużej niż obecne maksimum 2, więc odrzucamy dotychczasowy punkt końcowy i zastępujemy go bieżącą pozycją i wartością odwrotną:
endpoint = {position = 10, value = 19}
A potem idziemy na iteracji przez wyrażenie:
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
Tutaj okazuje się, że mamy kolejną sekwencję z 3 zerami, więc mamy porównanie bieżącej wartości odwróconych wartości punktu końcowego, a okaże się, że przechowywane końcowy ma niższą wartość:
endpoint.value = 2536 < reverse = 2826
więc utrzymać zestaw końcowy do pozycji 10 i idziemy na iteracji przez wyrażenie:
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
I znaleźć inną sekwencję 3 zerami, więc porównać tę pozycję do zapamiętanej punktu końcowego:
endpoint.value = 40584 > reverse = 35594
i uważamy, że ma mniejszą wartość, więc zastąpić możliwego punktu końcowego z aktualna pozycja:
endpoint = {position = 21, value = 35594}
A potem iteracyjne nad ostatecznym cyfry:
00011001000110100010001 unit = 524288; reverse = 35594 + 1 * 524288 = 559882
endpoint.value *= 2 + 1 = 71189
Więc u koniec okazuje się, że pozycja 21 daje nam najniższą wartość, więc jest to optymalne rozwiązanie:
00011001000110100010001 -> 00000010001011000100111
^ ^
start = 3 end = 21
Oto wersja C++, który wykorzystuje wektor bool zamiast liczb całkowitych. Może on parsować ciągi dłuższe niż 64 znaki, ale złożoność jest prawdopodobnie kwadratowa.
#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};
}
Czy odpowiedź nie jest zawsze taka, aby najpierw wpisać "a", a następnie "b"? –
@AbdenaceurLichiheb możesz wykonać operację dokładnie raz. Nie jest konieczne, abyś zawsze był na początku, a wszystkie b na końcu. na przykład 'S = ababba'' output = aabbab' – cryptomanic
tak Wiem, wyobraź sobie odpowiedź łańcuchową abababa to aaaabbb, wystarczy tylko policzyć liczbę "a" i "b", a następnie wpisać wszystkie "a", a następnie wszystkie "b". –