Top K Frequent Elements
Otrzymujesz tablicę liczb całkowitych nums oraz liczbę całkowitą k. Zwróć k wartości, które występują w nums najczęściej, w kolejności od najczęściej występującej. Jeśli dwie wartości występują tyle samo razy, pierwsza powinna być mniejsza z nich.
Każda wartość pojawia się w odpowiedzi tylko raz, niezależnie od tego, jak często występuje w nums, a k nigdy nie jest większe niż liczba różnych wartości.
Funkcja
- numsinteger-array
- wartości do zliczenia
- kinteger
- ile wartości zwrócić
- Zwracainteger-array
- k najczęściej występujących wartości, od najczęstszej do najrzadszej; w przypadku remisu najpierw mniejsza wartość
Ograniczenia
1 ≤ nums.length ≤ 104-104 ≤ nums[i] ≤ 1041 ≤ k, akjest co najwyżej liczbą różnych wartości wnums.
Przykłady
- Wejście
- nums = [4, 1, 4, 2, 1, 4, 3, 1, 4]k = 2
- Wyjście
- [4, 1]
- Wyjaśnienie
4występuje cztery razy,1trzy razy, a2i3po jednym razie. Dwie najczęściej występujące wartości to4, a następnie1.
- Wejście
- nums = [5, -2, 7, -2, 7, 5, 9]k = 2
- Wyjście
- [-2, 5]
- Wyjaśnienie
-2,5i7występują po dwa razy, a9raz. Trzy wartości zajmują pierwsze miejsce, więc odpowiedzią są dwie mniejsze:-2i5.
- Wejście
- nums = [8]k = 1
- Wyjście
- [8]
- Wyjaśnienie
- Jest jedna wartość, więc występuje najczęściej.
+16 ukrytych testów przy wysłaniu
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Zacznij od ustalenia, jak często występuje każda wartość. Która struktura danych przypisuje każdej wartości jej liczbę wystąpień w jednym przebiegu?
Mając już zliczenia, chcesz wybrać
knajlepszych wartości według jednego porządku: najpierw większa liczba wystąpień, a przy remisie mniejsza wartość. Posortowanie wszystkich różnych wartości się sprawdzi. Kopiec minimalny o rozmiarzekzachowuje tylko te wartości, które wciąż mogą znaleźć się w odpowiedzi.Licznik to liczba całkowita od 1 do
n. Utwórz jedno wiadro dla każdego licznika, przy czym wiadroczawiera wartości występujące dokładniecrazy, a następnie odczytuj wiadra od największego licznika do najmniejszego. Wypełniaj wiadra, przechodząc po wartościach od najmniejszej do największej, a każde wiadro będzie już uporządkowane według kolejności przy remisie.
Rozwiązanie
Zliczanie to ta szybsza połowa: jedno przejście z użyciem mapy mieszającej pozwala policzyć wystąpienia każdej wartości. Prawdziwe pytanie brzmi, jak wybrać k najlepszych wartości, nie wykonując więcej pracy, niż to konieczne. Posortowanie wszystkich d różnych wartości według liczby wystąpień kosztuje O(d log d), kopiec minimum o rozmiarze k zmniejsza ten koszt do O(d log k), a ponieważ liczba wystąpień jest liczbą całkowitą z zakresu od 1 do n, sortowanie kubełkowe porządkuje wartości według liczby wystąpień bez żadnych porównań.
Policz, a następnie posortuj według liczby
Intuicja
Najpierw zlicz wystąpienia. Jedno przejście z mapą haszującą przechowującą liczbę wystąpień każdej wartości zamienia [4, 1, 4, 2, 1, 4, 3, 1, 4] w 4 → 4, 1 → 3, 2 → 1, 3 → 1.
Następnie ułóż różne wartości w kolejności, w jakiej mają znaleźć się w odpowiedzi: najpierw większa liczba wystąpień, a przy tej samej liczbie wystąpień — mniejsza wartość. Ustaw sortowanie dokładnie według tego porównania: liczba wystąpień jako pierwsze kryterium, a wartość jako drugie. Pierwsze k elementów posortowanej listy to odpowiedź. Tutaj kolejność to 4, 1, 2, 3, a k = 2 oznacza, że zostają 4 i 1.
Zliczanie wymaga O(n). Sortowanie d różnych wartości wymaga O(d log d), a gdy każda wartość jest inna — najwyżej O(n log n): 10^4 wartości oznacza około 1.3 × 10^5 porównań, co jest szybkie. Marnotrawstwo polega na tym, że sortowanie porządkuje wszystkie wartości, choć liczy się tylko pierwsze k.
Algorytm
- Policz każdą wartość w mapie haszującej.
- Umieść unikalne wartości na liście.
- Posortuj listę według liczby wystąpień od największej do najmniejszej, a przy równych liczbach — według wartości od najmniejszej do największej.
- Zwróć pierwsze
kwartości.
from collections import Counter
def topKFrequent(nums, k):
counts = Counter(nums)
# Most frequent first; equal counts put the smaller value first.
ordered = sorted(counts, key=lambda value: (-counts[value], value))
return ordered[:k]Zachowaj k najlepszych elementów w kopcu minimum
Intuicja
Potrzebujesz tylko k najlepszych wartości, więc przechowuj tylko k kandydatów. Dla każdej nowej wartości trzeba sprawdzić, czy jest lepsza od najsłabszego przechowywanego kandydata. Kandydat jest słabszy, gdy ma mniejszą liczność albo taką samą liczność i większą wartość. Kopiec minimum uporządkowany według tej reguły trzyma na szczycie najsłabszego kandydata, którego odczyt zajmuje O(1), a zastąpienie O(log k).
Przejdź przez różne wartości. Gdy kopiec zawiera mniej niż k elementów, dodaj wartość. Potem wartość lepsza od elementu na szczycie zastępuje go, a wartość, która nie jest lepsza, zostaje pominięta, ponieważ przechowujesz już k lepszych wartości. Korzystając z kopca dostępnego w bibliotece, możesz skrócić kod: wstawiaj każdą wartość i usuwaj element ze szczytu za każdym razem, gdy kopiec przekroczy rozmiar k. W ten sposób zachowasz te same k wartości.
Na końcu kopiec zawiera wynik, ale nie jest on uporządkowany: kopiec jest tylko częściowo posortowany. Usuwanie elementów zwraca najpierw najsłabszą wartość, więc zapisuj wynik od ostatniej pozycji do pierwszej.
Każda z d różnych wartości wymaga co najwyżej jednej operacji na kopcu zawierającym k elementów, więc wybór zajmuje O(d log k). Jest to szybsze niż sortowanie, gdy k jest dużo mniejsze niż d, na przykład przy wybieraniu 10 najlepszych spośród 8000 różnych wartości.
Algorytm
- Policz wystąpienia każdej wartości w mapie haszującej.
- Dla każdej unikalnej wartości dodaj ją, dopóki kopiec nie zawiera mniej niż
kwartości. - Gdy kopiec jest pełny, porównaj wartość z elementem na szczycie, czyli najsłabszą zachowywaną wartością. Jeśli nowa wartość jest silniejsza, umieść ją na szczycie i przesiej w dół.
- Zdejmij element z kopca
krazy, za każdym razem wpisując wartość do wyniku, zaczynając od ostatniej pozycji i kończąc na pierwszej.
import heapq
from collections import Counter
def topKFrequent(nums, k):
counts = Counter(nums)
# Entries are (count, -value). heapq keeps the smallest entry on top, which is
# the weakest value kept: the lowest count, and on a tie the larger value.
heap = []
for value, count in counts.items():
heapq.heappush(heap, (count, -value))
if len(heap) > k:
heapq.heappop(heap)
# Pops come out weakest first, so fill the answer from the back.
result = [0] * k
for i in range(k - 1, -1, -1):
result[i] = -heapq.heappop(heap)[1]
return resultPolicz, a następnie posortuj kubełkowo według liczby wystąpień
Intuicja
Licznik nie może być dowolną liczbą: jest liczbą całkowitą od 1 do n. Dzięki temu można zastosować sortowanie kubełkowe. Utwórz jeden kubełek dla każdego licznika — kubełek c będzie zawierał wartości występujące dokładnie c razy — i odczytaj kubełki, zaczynając od kubełka n. Wartości pojawią się od najczęstszych, a żadnych dwóch liczników nie trzeba porównywać.
Reguła rozstrzygania remisów wymaga jeszcze jednej rzeczy: w kubełku najpierw musi znaleźć się mniejsza wartość. Wartości mieszczą się w przedziale od -10^4 do 10^4, więc do zliczania wystarczy tablica R = 2 × 10^4 + 1 liczników, w której wartość v znajduje się pod indeksem v + 10^4. Przejdź przez tę tablicę od najmniejszej do największej wartości i dodaj każdą wartość do kubełka odpowiadającego jej licznikowi. Każdy kubełek zapełnia się w kolejności rosnącej, czyli w kolejności rozstrzygania remisów, więc nie trzeba niczego sortować.
Dla [5, -2, 7, -2, 7, 5, 9] przejście umieszcza kolejno -2, 5 i 7 w kubełku 2, a 9 w kubełku 1. Odczytując kubełki od kubełka 7 w dół, napotykamy pierwszy kubełek z wartościami — kubełek 2 — a k = 2 oznacza wybór -2 i 5.
Wykonujemy jedno przejście po nums, jedno przejście po R licznikach i jedno przejście po kubełkach, czyli łącznie O(n + R): czas liniowy dla ustalonego zakresu wartości. Jeśli zamiast tablicy zliczającej użyjesz mapy mieszającej, zliczanie nadal będzie liniowe, ale kubełki będą zapełniane w kolejności mapy i trzeba będzie posortować każdy z nich, aby zachować regułę rozstrzygania remisów.
Algorytm
- Zlicz każde wystąpienie wartości w tablicy indeksowanej przez
value + 10^4. - Utwórz kubełki od 1 do
n, po jednej liście dla każdej możliwej liczby wystąpień. - Przejdź przez tablicę zliczającą od najmniejszej wartości do największej i dodaj każdą występującą wartość do kubełka odpowiadającego jej liczbie wystąpień.
- Odczytuj kubełki w kolejności od liczby wystąpień
ndo 1, pobierając wartości, aż uzyskaszkwartości.
def topKFrequent(nums, k):
OFFSET = 10000 # values run from -10^4 to 10^4
counts = [0] * (2 * OFFSET + 1)
for x in nums:
counts[x + OFFSET] += 1
# buckets[c] lists the values that occur exactly c times. Walking the
# values from smallest to largest fills every bucket in ascending order.
buckets = [[] for _ in range(len(nums) + 1)]
for i, c in enumerate(counts):
if c > 0:
buckets[c].append(i - OFFSET)
# Read the buckets from the highest count down until k values are taken.
result = []
for c in range(len(nums), 0, -1):
for value in buckets[c]:
result.append(value)
if len(result) == k:
return result
return result
Pułapki i przypadki brzegowe
Liczenie rzadko jest błędne. Błędna bywa kolejność odpowiedzi.
- Rozstrzyganie remisów na podstawie kolejności pierwszego wystąpienia lub kolejności w mapie haszującej. W drugim przykładzie
-2,5i7występują po dwa razy, a tylko reguła mniejszej wartości sprawia, że[-2, 5]jest jedyną poprawną odpowiedzią. - Zwracanie tablicy kopca w takiej postaci, w jakiej jest. Kopiec jest tylko częściowo uporządkowany, a jego szczyt zawiera najsłabszą wartość, czyli tę, która powinna znaleźć się na końcu.
- Odwracanie reguły rozstrzygania remisów w kopcu. Spośród dwóch wartości o tej samej liczności słabsza jest większa, więc kopiec minimalny dla
(count, value)usuwa niewłaściwą wartość. Użyj(count, -value)albo porównania zgodnego z regułą. - Tworzenie tylko tylu kubełków, ile jest różnych wartości. Jedna wartość może wystąpić
nrazy, jak w[3, 3, 3, 3], dlatego kubełeknmusi istnieć. - W Javie porównywanie dwóch liczności typu
Integerza pomocą!=. To porównuje referencje i powoduje błędy, gdy liczności przekroczą 127. Najpierw rozpakuj je do typuint. - Wybieranie całego kubełka na końcu. Zatrzymaj się, gdy tylko uzyskasz
kwartości, nawet w środku kubełka.
Najczęstsze pytania4
Jaka jest złożoność czasowa problemu Top K Frequent Elements?
Zliczanie zajmuje O(n). Wybór k największych elementów kosztuje następnie O(d log d) przy sortowaniu d różnych wartości, O(d log k) przy użyciu kopca minimum o rozmiarze k oraz O(n) i jednego przejścia po zakresie wartości przy sortowaniu kubełkowym. Ponieważ d może osiągnąć n, sortowanie ma w najgorszym przypadku złożoność O(n log n), a sortowanie kubełkowe jest liniowe.
Czy problem Top K Frequent Elements można rozwiązać w czasie O(n)?
Tak, za pomocą sortowania kubełkowego. Liczności są liczbami całkowitymi od 1 do n, więc każda wartość trafia do kubełka odpowiadającego jej liczności, a odczytanie kubełków od największej liczności do najmniejszej pozwala uporządkować wartości według częstotliwości bez sortowania przez porównania. Quickselect dla liczności ma również średnio złożoność O(n), ale w najgorszym przypadku jego złożoność jest kwadratowa.
Dlaczego używać kopca minimum, a nie kopca maksimum?
Max-kopiec ze wszystkich wartości d też się sprawdzi: zbuduj go w czasie O(d) i wykonaj operację pop k razy; łącznie zajmie to O(d + k log d). Min-kopiec o rozmiarze k przechowuje tylko k elementów i nadaje się do wartości napływających pojedynczo, ponieważ jego wierzchołek wskazuje element do usunięcia. Ceną jest to, że zwraca wynik w odwrotnej kolejności, więc wypełniasz wynik od końca.
Jak rozstrzyga się remisy w Top K Frequent Elements?
Wybierz jedną regułę i stosuj ją wszędzie; tutaj przy równych licznościach najpierw umieszczamy mniejszą wartość, dzięki czemu odpowiedź jest jednoznaczna. Przy sortowaniu porównuj liczności, a następnie wartości. W kopcu, spośród dwóch wartości o tej samej liczności, większa jest słabsza. Przy sortowaniu kubełkowym wypełniaj kubełki rosnąco według wartości, a wartości w każdym kubełku są już uporządkowane według reguły rozstrzygania remisów.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def topKFrequent(nums, k):
# Wpisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
nums = [4, 1, 4, 2, 1, 4, 3, 1, 4] k = 2
Oczekiwane
[4, 1]