Stworzyłem dwa rozwiązania dla leetcode problem 17, w których prosi się o wygenerowanie wszystkich możliwych ciągów tekstowych z kombinacji numerów telefonów, np. "3"
wyniki w ["d","e","f"]
.Analiza dwóch algorytmów Big-O
Moje pierwsze rozwiązanie wykorzystuje algorytm rekurencyjny do generowania ciągów i jest podany poniżej:
class Solution {
public:
void fill_LUT(vector<string>& lut) {
lut.clear();
lut.push_back(" ");
lut.push_back("");
lut.push_back("abc");
lut.push_back("def");
lut.push_back("ghi");
lut.push_back("jkl");
lut.push_back("mno");
lut.push_back("pqrs");
lut.push_back("tuv");
lut.push_back("wxyz");
}
void generate_strings(int index, string& digits, vector<string>& lut, vector<string>& r, string& work) {
if(index >= digits.size()) {
r.push_back(work);
return;
}
char idx = digits[index] - '0';
for(char c : lut[idx]) {
work.push_back(c);
generate_strings(index+1, digits, lut, r, work);
work.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> r;
vector<string> lut;
fill_LUT(lut);
if(digits.size() <= 0)
return r;
string work;
generate_strings(0, digits, lut, r, work);
return r;
}
};
jestem trochę zardzewiały z big-O, ale wydaje mi się, że złożoność przestrzeń byłaby O(n)
dla wywołanie rekurencyjne, tj. jego maksymalna głębokość, O(n)
dla ciągu bufora i O(n*c^n)
dla wynikowych ciągów. Czy to suma razem jako O(n+n*c^n)
?
Dla złożoności czasowej jestem nieco zdezorientowany. Każdy poziom rekursji wykonuje c
pcha + pops + rekursywne połączenia pomnożone przez liczbę operacji przez następny poziom, więc brzmi to jak c^1 + c^2 + ... + c^n
. Ponadto istnieją duplikaty długości c^n
z n
. Jak mogę to skonsolidować w ładne reprezentacje?
Drugie rozwiązanie postrzega liczbę wyników jako mieszany liczby radix i konwertuje go na sznurku, jak można wykonać int na hex konwersji strun:
class Solution {
public:
void fill_LUT(vector<string>& lut) {
lut.clear();
lut.push_back(" ");
lut.push_back("");
lut.push_back("abc");
lut.push_back("def");
lut.push_back("ghi");
lut.push_back("jkl");
lut.push_back("mno");
lut.push_back("pqrs");
lut.push_back("tuv");
lut.push_back("wxyz");
}
vector<string> letterCombinations(string digits) {
vector<string> r;
vector<string> lut;
fill_LUT(lut);
if(digits.size() <= 0)
return r;
unsigned total = 1;
for(int i = 0; i < digits.size(); i++) {
digits[i] = digits[i]-'0';
auto m = lut[digits[i]].size();
if(m > 0) total *= m;
}
for(int i = 0; i < total; i++) {
int current = i;
r.push_back(string());
string& s = r.back();
for(char c : digits) {
int radix = lut[c].size();
if(radix != 0) {
s.push_back(lut[c][current % radix]);
current = current/radix;
}
}
}
return r;
}
};
W tym przypadku, sądzi, że złożoność przestrzeni jest równa O(n*c^n)
podobna do pierwszego rozwiązania bez bufora i rekurencji, a złożoność czasu musi wynosić O(n)
dla pierwszej pętli for i dodatkowej O(n*c^n)
, aby utworzyć ciąg wyniku dla każdego z możliwych wyników. Ostatecznym big-O dla tego jest O(n+n*c^n)
. Czy mój proces myślowy jest poprawny?
Edit: dodać kilka wyjaśnień do kodu, wyobrazić ciąg wejściowy "234"
. Pierwsze rozwiązanie rekurencyjne wywoła generate_strings
z argumentami (0, "234", lut, r, work)
. lut
to tabela przeglądowa, która konwertuje liczbę na odpowiadające jej znaki. r
to wektor zawierający wynikowe ciągi. work
to bufor, w którym wykonywana jest praca.
Pierwsze wywołanie rekurencyjne wtedy zobaczyć, że cyfrowy wskaźnik 0
jest 2
który odpowiada "abc"
wcisnąć a
do work
, a następnie zadzwonić generate_strings
z argumentami (1, "234", lut, r, work)
. Po otrzymaniu połączenia urządzenie zostanie następnie przepchnięte b
do work
, a następnie spłucz i powtórz.
Gdy index
jest równy rozmiarowi digits
, generowany jest unikalny ciąg znaków, a ciąg znaków jest przesyłany do r
.
Dla drugiego rozwiązania, ciąg wejściowy jest najpierw konwertowany z reprezentacji ASCII na jego całkowitą reprezentację. Na przykład "234"
jest konwertowane na "\x02\x03\x04"
.Następnie kod wykorzystuje je jako indeksy do wyszukania liczby odpowiednich znaków w tablicy odnośników i oblicza całkowitą liczbę łańcuchów, które będą w wyniku. na przykład jeśli ciąg wejściowy to "234"
, 2
odpowiada , który ma 3 znaki. 3
odpowiada def
, który ma 3 znaki. 4
odpowiada ghi
, który ma 3 znaki. Całkowita liczba możliwych łańcuchów to 3*3*3 = 27
.
Następnie kod wykorzystuje licznik do reprezentowania każdego z możliwych ciągów. Jeśli i
byłby 15
, byłby oceniany przez najpierw znalezienie 15 % 3
, który jest 0
, odpowiadający pierwszemu znakowi dla pierwszej cyfry (a
). Następnie podziel 15
przez 3
, który jest 5
. 5 % 3
to 2
, który odpowiada trzeciemu znakowi drugiej cyfry, który jest f
. Na koniec podziel 5
przez 3
, a otrzymasz 1
. 1 % 3
to 1
, który odpowiada drugiemu znakowi trzeciej cyfry, h
. Dlatego ciąg znaków odpowiadający numerowi 15
jest afh
. Jest to wykonywane dla każdego numeru, a otrzymane łańcuchy są przechowywane w r
.
Czy możesz wyjaśnić pomysły algo w języku angielskim? Oszczędza dużo bólu głowy podczas czytania kodu. – cuongptnk
Kiedy masz O, c^n dominuje n. Możesz po prostu powiedzieć, że to O (c^n) i być poprawne. –
@cuongptnk pewnie, dodałem trochę więcej szczegółów na końcu mojego pytania. – Alden