Sort Colors
Otrzymujesz tablicę nums, w której każda wartość wynosi 0, 1 lub 2. Pomyśl o nich jak o trzech kolorach, na przykład czerwonym, białym i niebieskim. Przestaw elementy tablicy tak, aby najpierw znalazły się wszystkie 0, potem wszystkie 1, a na końcu wszystkie 2, i zwróć ją.
Rozwiąż to bez używania bibliotecznej funkcji sortującej. Chodzi o to, aby wykorzystać wiedzę o wartościach.
Funkcja
- numsinteger-array
- kolory, każdy z nich ma wartość 0, 1 lub 2
- Zwracainteger-array
- te same wartości, najpierw wszystkie 0, potem wszystkie 1, a następnie wszystkie 2
Ograniczenia
1 ≤ nums.length ≤ 1.5 × 104- Każde
nums[i]ma wartość0,1lub2. - Może brakować jednego koloru, a tablica może zawierać tylko jeden kolor.
Przykłady
- Wejście
- nums = [2, 1, 0, 2, 0, 1, 1]
- Wyjście
- [0, 0, 1, 1, 1, 2, 2]
- Wyjaśnienie
- Tablica zawiera dwie 0, trzy 1 i dwie 2, więc wynik jest dokładnie taki: dwie 0, potem trzy 1, a następnie dwie 2.
- Wejście
- nums = [2, 0, 2]
- Wyjście
- [0, 2, 2]
- Wyjaśnienie
- W ogóle nie ma jedynki. Pojedyncze 0 przesuwa się na początek, a za nim pojawiają się dwie dwójki.
- Wejście
- nums = [1]
- Wyjście
- [1]
- Wyjaśnienie
- Pojedyncza wartość jest już uporządkowana, więc tablica pozostaje bez zmian.
+17 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Co byś zmienił, gdyby było k kolorów zamiast trzech, przy czym k byłoby znacznie mniejsze niż długość tablicy?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Mogą wystąpić tylko trzy różne wartości. Co możesz dzięki temu zrobić, czego nie umożliwia ogólne sortowanie?
Zliczanie 0, 1 i 2 oraz przepisanie tablicy odbywa się w dwóch przebiegach. W jednym przebiegu wyobraź sobie, że trzy obszary powiększają się jednocześnie: 0 z przodu, 2 z tyłu, a 1 pomiędzy nimi.
Utrzymuj trzy indeksy:
low,midihigh. Odczytajnums[mid]: 0 zamień z elementem na pozycjilow, 2 zamień z elementem na pozycjihigh, a 1 pozostaw bez zmian. Po zamianie z elementem na pozycjihighponownie odczytaj tę samą pozycję.
Rozwiązanie
Każde sortowanie daje właściwą kolejność, więc prawdziwe pytanie brzmi: co pozwalają pominąć te trzy wartości? Ponieważ mogą wystąpić tylko 0, 1 i 2, możesz je policzyć i przepisać tablicę w dwóch przebiegach. Dzięki trzem wskaźnikom, które wyznaczają koniec ciągu zer i początek ciągu dwójek, możesz nawet umieścić każdą wartość na swoim miejscu w jednym przebiegu. Ten podział w jednym przebiegu to algorytm flagi narodowej Holandii.
Sortowanie bąbelkowe ręcznie
Poprawne, ale nie kończy się na największych testach
Intuicja
Sortowanie za pomocą biblioteki działałoby w czasie O(n log n), ale reguły zadania tego zabraniają, ponieważ osoba prowadząca rozmowę chce zobaczyć, co zrobisz, wiedząc, że występują tylko trzy wartości. Podstawowym rozwiązaniem jest więc sortowanie napisane samodzielnie, a najprościej poprawnie zaimplementować sortowanie bąbelkowe: przejdź po tablicy i za każdym razem, gdy dwa sąsiednie elementy są w złej kolejności, zamień je miejscami.
Jedno przejście przesuwa największą napotkaną wartość aż na koniec, niczym unoszący się bąbelek. Po pierwszym przejściu ostatnia pozycja jest już ustalona, po drugim ustalone są już dwie ostatnie pozycje, więc n-1 przejść wystarczy, by cała tablica była uporządkowana. W [2, 1, 0] pierwsze przejście przesuwa 2 na koniec, dając [1, 0, 2], a drugie zamienia miejscami 1 i 0.
To sortowanie jest powolne, ponieważ każde przejście porównuje wszystkie pary, które nie są jeszcze na właściwych pozycjach: łącznie około n²/2 porównań. Dla n = 1.5 × 10^4 oznacza to ponad 10^8 porównań, a także zamianę miejscami każdej pary, która początkowo jest w złej kolejności. Żadna z tych operacji nie wykorzystuje faktu, że występują tylko trzy wartości.
Algorytm
- Wykonaj n-1 przebiegów po tablicy.
- W każdym przebiegu porównaj każdą parę sąsiadów
nums[j]inums[j + 1], która nie jest jeszcze na końcowej pozycji, i zamień je miejscami, gdy lewy element jest większy. - Po przebiegu numer
done(licząc od 0) ostatniedone + 1pozycje zawierają już swoje końcowe wartości, więc następny przebieg kończy się przed nimi. - Zwróć
nums.
def sortColors(nums):
n = len(nums)
for done in range(n - 1):
# One pass: the largest value left so far bubbles to index n-1-done.
for j in range(n - 1 - done):
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
return numsPolicz każdy kolor, a następnie zapisz ponownie
Intuicja
Sortowanie bąbelkowe poświęca cały czas na porównywanie sąsiadujących elementów, ale już wiesz, jakie wartości występują. Jeśli tablica zawiera dwie wartości 0, trzy wartości 1 i dwie wartości 2, wynik jest ustalony, zanim cokolwiek przesuniesz: dwie wartości 0, trzy wartości 1, dwie wartości 2. Liczą się tylko liczności.
Przejdź więc raz przez tablicę i policz każdą wartość. Następnie nadpisz ją od początku: count[0] zer, potem count[1] jedynek, a następnie count[2] dwójek. To sortowanie przez zliczanie, które jest tutaj bezpieczne, ponieważ równe wartości są zamienne. 1 to 1, więc pierwotna kolejność nie musi zostać zachowana.
To dwa przebiegi i trzy liczniki, czas O(n) i pamięć O(1). Spełnia ograniczenia i jest naturalnym rozwiązaniem, gdy kolorów jest wiele. Dalsze pytanie, z którego słynie to zadanie, brzmi: czy da się to zrobić, odczytując tablicę tylko raz?
Algorytm
- Utwórz trzy liczniki, wszystkie ustawione na 0.
- Odczytaj każdą wartość i zwiększ jej licznik o jeden.
- Zapisz
count[0]zer od początku, następniecount[1]jedynek, a potemcount[2]dwójek. - Zwróć
nums.
def sortColors(nums):
count = [0, 0, 0] # how many 0s, 1s and 2s
for x in nums:
count[x] += 1
i = 0
for color in range(3):
for _ in range(count[color]):
nums[i] = color
i += 1
return numsJedno przejście z trzema wskaźnikami (flaga Holandii)
Intuicja
Przesuwaj trzy obszary podczas odczytywania: 0 z przodu, 1 za nimi, 2 z tyłu, a między 1 i 2 pozostaw nieodczytaną część. Trzy indeksy wyznaczają granice. Wszystko przed low to 0, wszystko od low do mid (bez mid) to 1, wszystko za high to 2, a elementy od nums[mid] do nums[high] są jeszcze nieodczytane.
Odczytaj nums[mid]. 1 jest już w swoim obszarze, więc przesuń mid dalej. 0 należy umieścić z przodu: zamień go z nums[low] i przesuń dalej zarówno low, jak i mid. Wartość, która wraca z low, to 1 (albo to samo 0, jeśli nie napotkano jeszcze żadnej 1), więc jest już na swoim miejscu. 2 należy umieścić z tyłu: zamień go z nums[high] i przesuń high wstecz, ale pozostaw mid na miejscu, ponieważ wartość, która przybyła z high, nie została jeszcze odczytana.
Każdy krok przesuwa mid do przodu albo high wstecz, więc nieodczytana część za każdym razem zmniejsza się o jeden element, a pętla kończy się po n krokach. Prześledź [2, 0, 2]: pierwsza 2 zostaje zamieniona z ostatnią 2, a high zmniejsza się do 1; indeks 0 nadal zawiera 2, która zostaje zamieniona z 0, a high zmniejsza się do 0; indeks 0 zawiera teraz 0, który pozostaje na miejscu, i otrzymujesz [0, 2, 2].
Algorytm
- Ustaw
low = 0,mid = 0ihighna ostatni indeks. - Gdy
mid ≤ high, odczytajnums[mid]. - Jeśli wynosi 0, zamień go z
nums[low]i przesuńloworazmido jeden krok w prawo. - Jeśli wynosi 1, przesuń
mido jeden krok w prawo. - Jeśli wynosi 2, zamień go z
nums[high]i przesuńhigho jeden krok w lewo. Pozostawmidna miejscu. - Zwróć
nums.
def sortColors(nums):
# nums[:low] are 0s, nums[low:mid] are 1s, nums[high + 1:] are 2s.
low, mid, high = 0, 0, len(nums) - 1
while mid <= high:
if nums[mid] == 0:
nums[low], nums[mid] = nums[mid], nums[low]
low += 1
mid += 1
elif nums[mid] == 1:
mid += 1
else:
# The value swapped in from high is unread, so mid stays.
nums[mid], nums[high] = nums[high], nums[mid]
high -= 1
return nums
Pułapki i przypadki brzegowe
Wersja jednoprzebiegowa jest krótka, a niemal każdy błąd w niej wynika z przesunięcia wskaźnika, który nie powinien się przesunąć.
- Przesunięcie
middo przodu po zamianie zhigh. Otrzymana wartość nie została odczytana. W przypadku[1, 2, 0]wartość 2 zamienia się miejscami z 0, a pominięcie 0 daje w wyniku[1, 0, 2]. - Wykonywanie pętli, dopóki
mid < high, gdyhighjest indeksem ostatniego nieodczytanego elementu. Gdy oba wskaźniki się spotykają, ten element nadal nie został odczytany. W przypadku[1, 0]pętla zatrzymuje się, zanim odczyta 0, i zwraca[1, 0]. - Pozwolenie, by
highspadło poniżej zera przy użyciu indeksu bez znaku. Tablica zawierająca wyłącznie wartości 2, taka jak[2], powoduje, żehighprzyjmuje wartość -1. W Rust, gdzie indeksy mają typusize, zamiast tego utrzymujhigho jeden indeks za nieodczytaną częścią, tak jak robi to kod w Rust. - Założenie, że występuje każdy kolor. W
[2, 0, 2]nie ma 1, a tablica może zawierać tylko jeden kolor. Reguły dotyczące wskaźników obsługują oba przypadki bez wyjątków, więc żadnych nie dodawaj.
Najczęstsze pytania4
Czym jest problem flagi narodowej Holandii?
Edsger Dijkstra postawił problem: mając w rzędzie obiekty w trzech kolorach — czerwonym, białym i niebieskim, jak na fladze Holandii — pogrupuj obiekty każdego koloru w jednym przebiegu, używając wyłącznie zamian. Sort Colors to ten sam problem z liczbami 0, 1 i 2. Jego rozwiązaniem jest podział za pomocą trzech wskaźników: low, mid i high.
Jaka jest złożoność czasowa i pamięciowa Sort Colors?
Rozwiązanie jednoprzebiegowe działa w czasie O(n), ponieważ każdy krok zmniejsza nieodczytaną część o jedną komórkę. Wykorzystuje O(1) dodatkowej pamięci: trzy indeksy i tymczasową wartość do zamiany. Sortowanie przez zliczanie ma takie same ograniczenia, ale odczytuje tablicę dwukrotnie.
Dlaczego mid nie przesuwa się po zamianie z high?
Wartość, która wraca z high, nigdy nie została odczytana, więc może być równa 0, 1 lub 2. Przesunięcie mid dalej poza nią pozostawiłoby 0 lub 2 na środku. Zamiana z low jest inna: wszystko między low a mid jest równe 1, więc wiadomo, jaka wartość wraca, i mid może przesunąć się dalej.
Czy sortowanie przez zliczanie jest akceptowalnym rozwiązaniem zadania „Sort Colors”?
Spełnia ograniczenia czasowe O(n) i pamięciowe O(1), a wielu rekruterów akceptuje to jako pierwszą odpowiedź. Spodziewaj się dodatkowego pytania o rozwiązanie w jednym przebiegu, czyli podział z użyciem trzech wskaźników. Zliczanie jest lepszym narzędziem, gdy występuje wiele kolorów, ponieważ podział rozdziela tylko na trzy grupy.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def sortColors(nums):
# Napisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
nums = [2, 1, 0, 2, 0, 1, 1]
Oczekiwane
[0, 0, 1, 1, 1, 2, 2]