Container With Most Water
Otrzymujesz listę height nieujemnych liczb całkowitych. Linia i to pionowa ściana o wysokości height[i], stojąca na pozycji i. Dowolne dwie linie tworzą z podłożem pojemnik, który mieści tyle wody, ile wynosi wysokość niższej linii pomnożona przez odległość między tymi liniami. Pozostałe linie nie przeszkadzają. Zwróć największą ilość wody, jaką może pomieścić jedna para linii.
Funkcja
- heightinteger-array
- wysokości linii na pozycjach 0, 1, 2 i tak dalej
- Zwracainteger
- najwięcej wody, jaką mogą pomieścić dwie linie
Ograniczenia
2 ≤ height.length ≤ 1040 ≤ height[i] ≤ 104- Wynik jest nie większy niż 108, więc mieści się w 32-bitowej liczbie całkowitej.
Przykłady
- Wejście
- height = [3, 7, 2, 5, 4, 7, 3, 6]
- Wyjście
- 36
- Wyjaśnienie
- Linie na pozycjach 1 i 7 mają wysokości 7 i 6 oraz są oddalone od siebie o 6, więc pomieszczą 6 × 6 = 36. Dwie najwyższe linie, siódemki na pozycjach 1 i 5, pomieszczą tylko 7 × 4 = 28, a skrajna para pomieści 3 × 7 = 21.
- Wejście
- height = [4, 4]
- Wyjście
- 4
- Wyjaśnienie
- Dwie linie tworzą dokładnie jeden pojemnik: o wysokości 4 i szerokości 1, więc mieści 4.
+15 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Tutaj linie między wybranymi przez Ciebie dwiema są ignorowane. Gdyby każda linia była pełnym słupkiem, ile wody zebrałoby się między nimi wszystkimi? Czy potrafisz obliczyć to również w O(n)?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Zacznij od dwóch skrajnych linii: tworzą one najszerszy pojemnik. Przesunięcie dowolnego końca do środka zmniejsza szerokość o jedną jednostkę. Która z tych dwóch linii może to zrekompensować?
Wysokość wody ogranicza krótsza linia. Przesunięcie wyższej linii do środka zachowuje to ograniczenie i zmniejsza szerokość, więc nigdy nie może pomóc. Szansę daje tylko zastąpienie krótszej linii.
Trzymaj wskaźnik na każdym końcu. Zmierz ilość wody między nimi i zachowaj najlepszy wynik, a następnie przesuń wskaźnik przy krótszej linii o jedno miejsce do środka. Zatrzymaj się, gdy wskaźniki się spotkają.
Rozwiązanie
Istnieje około n²/2 par linii, więc przy 10^4 liniach sprawdzenie wszystkich oznacza wykonanie 5 × 10^7 mnożeń. Rozwiązaniem jest to, że ilość wody zależy tylko od krótszej linii z pary: gdy już wiesz, że dana linia jest krótszym bokiem najszerszego pojemnika, jaki może utworzyć, żaden węższy pojemnik, który ją wykorzystuje, nie pomieści więcej wody. Dwa wskaźniki pozwalają wykorzystać ten fakt i przejść tablicę od obu końców w jednym przebiegu.
Sprawdź każdą parę
Poprawne, ale nie kończy się na największych testach
Intuicja
Każdy pojemnik to para pozycji i < j. Woda podnosi się, aż przeleje się przez niższą ściankę, a dno między ściankami ma szerokość j - i, więc para mieści min(height[i], height[j]) × (j - i). Sprawdź każdą parę, zachowaj największą wartość i z definicji otrzymasz odpowiedź.
Problemem jest liczba par. n linii daje n(n-1)/2 par: około 5 × 10^7 dla 10^4 linii, a za każdym razem, gdy lista się podwaja, ich liczba wzrasta czterokrotnie. Język kompilowany wykona to w ułamku sekundy, ale Python, Ruby lub R potrzebują wielu sekund, a liczba par rośnie zbyt szybko dla każdego języka, gdy n osiąga 10^5.
Algorytm
- Ustaw
bestna 0. - Dla każdego
ioraz każdegojpo nim obliczmin(height[i], height[j]) × (j - i). - Zachowaj większą z wartości:
besti obliczoną wartość. - Zwróć
best.
def maxArea(height):
n = len(height)
best = 0
for i in range(n):
for j in range(i + 1, n):
water = min(height[i], height[j]) * (j - i)
best = max(best, water)
return bestNajpierw najwyższe linie
Intuicja
Spójrz na pojemnik od strony jego krótszego boku. Jeśli bok i jest krótszy, pojemnik mieści height[i] razy odległość, a jego drugim bokiem może być dowolny bok o co najmniej takiej samej wysokości. Najlepszy pojemnik, w którym i jest krótszym bokiem, tworzy więc z nim najdalszy bok o co najmniej takiej wysokości.
Aby szybko znaleźć takie drugie boki, ustaw boki od najwyższego do najniższego. Gdy przychodzi kolej na bok i, każdy bok ustawiony przed nim ma co najmniej taką samą wysokość, a najdalszy z nich znajduje się albo na skrajnym lewym, albo na skrajnym prawym spośród ustawionych indeksów. Śledź te dwa indeksy: lo i hi. Bok i może wtedy pomieścić najwyżej height[i] × max(i - lo, hi - i). Odpowiedzią jest największa z tych wartości, ponieważ najlepszy pojemnik zostaje uwzględniony, gdy przychodzi kolej na jego krótszy bok.
W pierwszym przykładzie dwa boki o wysokości 7 na pozycjach 1 i 5 pojawiają się jako pierwsze i mieszczą 28. Następnie pojawia się bok o wysokości 6 na pozycji 7, z lo = 1 i hi = 5; mieści on 6 × 6 = 36. Żaden niższy bok nie daje lepszego wyniku. Boki o równej wysokości mogą pojawiać się w dowolnej kolejności: ten z dwóch równych boków, który pojawi się jako drugi, zobaczy pierwszy jako możliwy drugi bok.
Sortowanie zajmuje O(n log n), a przejście O(n), co jest wystarczająco szybkie. Nadal potrzeba O(n) pamięci na kolejność, a następne podejście pozwala zrezygnować zarówno z sortowania, jak i z tej pamięci.
Algorytm
- Posortuj indeksy według wysokości, od najwyższego.
- Ustaw
loihina pierwszy indeks w tej kolejności, abestna 0. - Dla każdego następnego indeksu
iobliczheight[i]pomnożone przez większą z wartościi - loihi - i, a następnie zachowaj najlepszą wartość. - Zaktualizuj
loihi, aby uwzględnići. - Zwróć
best.
def maxArea(height):
# Indices from the tallest line to the shortest.
order = sorted(range(len(height)), key=lambda i: height[i], reverse=True)
lo = hi = order[0] # leftmost and rightmost index among the lines placed so far
best = 0
for i in order[1:]:
# Every placed line is at least as tall as line i, so line i is the
# shorter side, and its best partner is the placed line farthest away.
best = max(best, height[i] * max(i - lo, hi - i))
lo = min(lo, i)
hi = max(hi, i)
return bestDwa wskaźniki z obu końców
Intuicja
Zacznij od najszerszego pojemnika: left = 0 i right = n-1, a następnie zmierz jego pojemność. Teraz można odsunąć jedną z dwóch linii, a wybór jest oczywisty: odsuń krótszą. Załóżmy, że height[left] ≤ height[right]. Każdy inny pojemnik wykorzystujący linię left łączy ją z linią położoną bliżej niż right, więc jest węższy, a jego wysokość nadal wynosi co najwyżej height[left]. Żaden z nich nie pomieści więcej wody niż zmierzony pojemnik, więc linia left jest już rozpatrzona, a left przesuwa się o jeden krok w prawo. Przesunięcie zamiast niej wyższej linii zachowałoby ten sam limit wysokości i zmniejszyłoby szerokość, więc wynik może być tylko gorszy. Gdy wysokości obu linii są równe, obie są już rozpatrzone, więc można przesunąć dowolną z nich.
W każdym kroku jedna linia zostaje na stałe wyeliminowana, więc po n-1 krokach wskaźniki się spotykają. Najlepsza para nigdy nie zostaje pominięta: gdy jedna z jej dwóch linii zostaje odsunięta po raz pierwszy, zmierzony w tym momencie pojemnik mieści co najmniej tyle samo wody.
Dla [3, 7, 2, 5, 4, 7, 3, 6] pozycje 0 i 7 mieszczą 3 × 7 = 21. Linia o wysokości 3 jest krótsza, więc left przesuwa się na pozycję 1. Pozycje 1 i 7 mieszczą 6 × 6 = 36, a teraz krótsza jest linia o wysokości 6, więc right przesuwa się na pozycję 6. Kolejne pojemniki mieszczą 15, 28, 12, 10 i 2, więc wynik pozostaje równy 36.
Algorytm
- Ustaw
left = 0,right = n-1ibest = 0. - Dopóki
left < right, obliczmin(height[left], height[right]) × (right - left)i zachowaj najlepszą wartość. - Jeśli
height[left] < height[right], przesuńlefto jeden krok w prawo. W przeciwnym razie przesuńrighto jeden krok w lewo. - Gdy wskaźniki się spotkają, zwróć
best.
def maxArea(height):
left, right = 0, len(height) - 1
best = 0
while left < right:
water = min(height[left], height[right]) * (right - left)
best = max(best, water)
# The shorter line cannot do better with any line closer in, so drop it.
if height[left] < height[right]:
left += 1
else:
right -= 1
return best
Pułapki i przypadki brzegowe
Pętla z dwoma wskaźnikami jest krótka, więc błędy kryją się w szczegółach.
- Przesuwanie wyższej linii. W pierwszym przykładzie daje to 21 zamiast 36: 6 na pozycji 7 to wyższa linia z pierwszej pary, więc zostaje przesunięta, zanim zdąży spotkać 7 na pozycji 1.
- Używanie wyższej linii lub średniej z obu jako wysokości. Woda przelewa się przez niższą ścianę, więc wysokością jest minimum.
- Błąd o jeden w szerokości. Linie na pozycjach
iijsą oddalone od siebie oj - i, a niej - i + 1, więc dwie sąsiadujące linie mieszczą wodę o wysokości równej niższej z nich pomnożonej przez 1. - Zakładanie, że odpowiedź wykorzystuje najwyższą linię lub skrajną parę. W pierwszym przykładzie dwie siódemki mieszczą 28, a skrajna para 21, podczas gdy odpowiedź wynosi 36.
- Przepełnienie przy większych limitach. Tutaj ilość wody pozostaje poniżej 10^8, ale przy wysokościach i długościach bliskich 10^5 iloczyn przekracza 2^31 i wymaga 64-bitowej liczby całkowitej.
Najczęstsze pytania4
Jaka jest złożoność czasowa problemu „Container With Most Water”?
Rozwiązanie z dwoma wskaźnikami działa w czasie O(n) i wymaga O(1) dodatkowej pamięci. W każdym kroku jeden wskaźnik przesuwa się o jedną pozycję do środka, więc wykonuje się najwyżej n-1 kroków. Sprawdzenie każdej pary zajmuje O(n²), a posortowanie linii według wysokości — O(n log n).
Dlaczego przesunąć wskaźnik do krótszej linii?
Wysokość wody ogranicza krótsza linia. Każdy inny pojemnik, w którym znajduje się ta linia, ma drugą linię bliżej, więc jest węższy i nadal nie jest wyższy niż krótsza linia. Żaden z nich nie może być lepszy od zmierzonego przez Ciebie pojemnika, więc możesz pominąć krótszą linię bez utraty odpowiedzi.
Czy Container With Most Water jest problemem zachłannym?
Tak. Każdy krok polega na lokalnym wyborze, którego nigdy się nie cofa, i odrzuceniu krótszej linii. Wybór jest bezpieczny, ponieważ każdy pojemnik, który krok wyklucza, nie jest lepszy od żadnego z już zmierzonych. Dlatego ten problem zalicza się zarówno do zachłannych algorytmów, jak i do metody dwóch wskaźników.
Jak problem „Container With Most Water” różni się od problemu „Trapping Rain Water”?
Tutaj liczą się tylko dwie wybrane linie, a linie między nimi są ignorowane, więc odpowiedzią jest pojedynczy prostokąt. W problemie Trapping Rain Water każdy słupek jest pełny, a woda gromadzi się nad każdym słupkiem do wysokości niższego z najwyższych słupków po jego obu stronach, więc odpowiedź jest sumą dla wszystkich pozycji. Oba problemy mają rozwiązania z dwoma wskaźnikami o złożoności O(n), ale reguły przesuwania wskaźników i to, co sumujesz, są różne.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def maxArea(height):
# Napisz kod tutajPrzypadek 1
Przypadek 2
Wejście
height = [3, 7, 2, 5, 4, 7, 3, 6]
Oczekiwane
36