Longest Subarray with Sum K
Dla tablicy liczb całkowitych nums i wartości docelowej k zwróć długość najdłuższej podtablicy, której elementy sumują się dokładnie do k. Podtablica to ciąg kolejnych elementów o długości co najmniej jeden. Niektóre wartości mogą być równe zero lub mniejsze od zera. Jeśli żadna podtablica nie sumuje się do k, zwróć 0.
Funkcja
- numsinteger-array
- liczby całkowite do wyszukania, w tym liczby ujemne i zera
- kinteger
- dokładna suma, jaką musi mieć podtablica
- Zwracainteger
- długość najdłuższej podtablicy, której suma wynosi k, lub 0, jeśli taka nie istnieje
Ograniczenia
1 ≤ nums.length ≤ 2 × 104-104 ≤ nums[i] ≤ 104-109 ≤ k ≤ 109- Każda suma prefiksowa mieści się w zakresie od
-2 × 108do2 × 108, więc wystarczą liczby całkowite 32-bitowe. - Zwróć
0, gdy żadna podtablica nie sumuje się dok.
Przykłady
- Wejście
- nums = [2, -1, 3, 1, -4, 2, 3]k = 5
- Wyjście
- 5
- Wyjaśnienie
- Trzy ciągi dają w sumie 5:
[2, 3]na końcu,[2, -1, 3, 1]na początku oraz[3, 1, -4, 2, 3]na indeksach od 2 do 6. Ostatni jest najdłuższy i zawiera 5 elementów.
- Wejście
- nums = [4, 1, 1, 2, -1, 1]k = 3
- Wyjście
- 4
- Wyjaśnienie
[1, 1, 2, -1]na indeksach od 1 do 4 oraz[1, 2, -1, 1]na indeksach od 2 do 5 sumują się do 3 i zawierają po 4 elementy. Zwracasz długość, więc remis nie ma znaczenia.
- Wejście
- nums = [-3, 6, -3]k = 1
- Wyjście
- 0
- Wyjaśnienie
- Sześć sum podtablic to -3, 6, -3, 3, 3 i 0. Żadna z nich nie wynosi 1, więc odpowiedź to 0.
Pytanie dodatkowe
Jak znaleźć najdłuższą podtablicę, której suma jest nie większa niż k, przy czym wartości ujemne są nadal dozwolone?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Przy 20 000 liczbach metoda brute force sumuje około 2 × 10^8 par początkowych i końcowych indeksów. Wartości ujemne sprawiają też, że okno przesuwne nie działa: suma, która przekracza
k, może później znowu spaść do tego poziomu. Jaką sumę bieżącą można wykorzystać, aby obliczyć sumę dowolnej podtablicy za pomocą jednego odejmowania?Przy użyciu bieżących sum prefiksowych elementy po pozycji
iaż do pozycjijsumują się doprefix[j] - prefix[i]. Podtablica kończąca się najma sumęk, gdy wcześniejszy prefiks jest równyprefix[j] - k. Jeśli pasuje kilka wcześniejszych prefiksów, który z nich daje najdłuższą podtablicę?Zapisz dla każdej sumy bieżącej pierwszy indeks, pod którym się pojawiła, zaczynając od sumy 0 pod indeksem -1. Przy każdym indeksie wyszukaj
prefix - ki porównaj odległość z najlepszą długością. Dodaj bieżący prefiks do mapy tylko wtedy, gdy jeszcze go w niej nie ma.
Rozwiązanie
Sumy skumulowane sprowadzają sumę dowolnej podtablicy do jednego odejmowania: elementy po indeksie s aż do indeksu i dają w sumie prefix[i] - prefix[s]. Zapamiętaj pierwszy indeks, pod którym pojawia się każda suma, a jedno przejście znajdzie najdłuższe dopasowanie kończące się na każdej pozycji.
Rozwijaj każdą podtablicę od każdego początku
Poprawne, ale nie kończy się na największych testach
Intuicja
Z każdego początku rozszerzaj zakres w prawo i dodawaj każdy element do bieżącej sumy. Za każdym razem, gdy suma jest równa k, zapisz długość. Każda podtablica odpowiada jednej parze początku i końca, więc żadna nie zostanie pominięta. Rozszerzaj zakres dalej, nawet gdy suma przekroczy k: od indeksu 2 w pierwszym przykładzie sumy wynoszą 3, 4, 0, 2, 5, a to dzięki -4 ostatnia osiąga 5.
Istnieje około n²/2 par: 2 × 10^8 dodawań dla n = 2 × 10^4.
Algorytm
- Ustaw
bestna 0. - Dla każdego
startustawtotalna 0. - Przesuwaj
endodstartdo ostatniego indeksu i dodawajnums[end]dototal. - Gdy
totaljest równek, ustawbestna większą z wartościbestiend - start + 1. - Zwróć
best.
def longestSubarrayWithSumK(nums, k):
n = len(nums)
best = 0
for start in range(n):
total = 0
for end in range(start, n):
total += nums[end]
# A match may come after the total has passed k, so never stop early
if total == k and end - start + 1 > best:
best = end - start + 1
return bestSumy prefiksowe z pierwszym indeksem każdej sumy
Intuicja
Przedział kończący się na indeksie i sumuje się do k dokładnie wtedy, gdy wcześniejsza suma jest równa prefix - k. Jeśli kwalifikuje się kilka wcześniejszych sum, pierwsza z nich rozpoczyna najdłuższy przedział, dlatego mapa przechowuje każdą sumę tylko przy jej pierwszym wystąpieniu. Późniejsza kopia mogłaby rozpocząć jedynie krótszy przedział. Zainicjuj mapę sumą 0 na indeksie -1, aby dopasowanie rozpoczynające się na indeksie 0 miało długość i + 1.
W pierwszym przykładzie sumy wynoszą 2, 1, 4, 5, 1, 3, 6. Na indeksie 6 wartość 6 - 5 = 1 pojawiła się po raz pierwszy na indeksie 1, co daje długość 5. Suma 1 pojawia się ponownie na indeksie 4, a zachowanie indeksu 1 pozwala znaleźć ten przedział.
Jedno wyszukiwanie na element daje czas O(n), a mapa przechowuje najwyżej n + 1 sum.
Algorytm
- Utwórz mapę
first, przechowującą sumę 0 pod indeksem -1, i ustawprefixorazbestna 0. - Dla każdego indeksu
idodajnums[i]doprefix. - Jeśli
prefix - kznajduje się w mapie pod indeksemstart, ustawbestna większą z wartościbestii - start. - Jeśli
prefixnie ma jeszcze w mapie, zapisz go pod indeksemi. - Zwróć
best.
def longestSubarrayWithSumK(nums, k):
# first[p] is the index where the prefix sum p first appears; the empty prefix sits at -1
first = {0: -1}
prefix = 0
best = 0
for i, num in enumerate(nums):
prefix += num
# The earliest prefix equal to prefix - k starts the longest match that ends at i
start = first.get(prefix - k)
if start is not None and i - start > best:
best = i - start
# Keep the first index only: a later one could only start a shorter subarray
if prefix not in first:
first[prefix] = i
return best
Pułapki i przypadki brzegowe
- Nadpisywanie za każdym razem zapisanego indeksu, gdy suma częściowa się powtarza, zachowuje najnowszy początek i pozwala znaleźć krótszy pasujący fragment. Pierwszy przykład zwraca 4 zamiast 5, ponieważ suma częściowa 1 przenosi się z indeksu 1 na indeks 4.
- Okno, które się zmniejsza, gdy jego suma jest większa niż
k, również zwraca 4 w pierwszym przykładzie. Kończy, zawierając[-1, 3, 1, -4, 2, 3], z sumą 4, i nigdy nie usuwa -1. - Pominięcie sumy częściowej 0 na indeksie -1 powoduje pominięcie każdego pasującego fragmentu zaczynającego się na indeksie 0:
[5, 0, 0, 0]przyk = 5zwraca 0 zamiast 4. - Zapisany indeks to pozycja przed podtablicą, więc jej długość wynosi
i - start. Dodanie 1 powoduje policzenie o jednego elementu za dużo.
Najczęstsze pytania3
Jaka jest złożoność czasowa problemu najdłuższego podciągu o sumie K?
Czas O(n) i dodatkowa pamięć O(n) dzięki sumom narastającym i mapie pierwszych indeksów: jedno wyszukanie i najwyżej jedno wstawienie na element. Rozwijanie każdego podciągu od każdego początku kosztuje O(n²) czasu i O(1) pamięci.
Dlaczego metoda okna przesuwnego zawodzi, gdy tablica zawiera liczby ujemne?
Metoda przesuwnego okna zakłada, że dodanie elementu zwiększa sumę, a usunięcie jej zmniejsza. Przy wartościach ujemnych okno, którego suma jest zbyt duża, może nadal osiągnąć k, gdy pojawi się element ujemny, a okno, którego suma jest zbyt mała, może wymagać usunięcia ujemnego elementu z lewej strony. Okno nie jest w stanie określić, który ruch jest właściwy.
Czy problem znalezienia najdłuższej podtablicy o sumie K można rozwiązać przy użyciu O(1) dodatkowej pamięci?
Tylko wtedy, gdy żadna wartość nie jest ujemna. Wówczas suma nigdy nie maleje wraz ze zwiększaniem okna, a dwa wskaźniki znajdują najdłuższe okno o sumie k w czasie O(n) i przy użyciu O(1) pamięci. W przypadku wartości ujemnych potrzebujesz mapy sum albo skanowania O(n²).
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
def longestSubarrayWithSumK(nums, k):
# Wpisz tutaj kodTest #1
Test #2
Test #3
Argumenty
nums = [2, -1, 3, 1, -4, 2, 3] k = 5
Oczekiwany wynik
5