Plecak bez ograniczeń i Coin Change II
Zezwalać na wielokrotne użycie elementów przez iterowanie pojemności w przód oraz rozwiązywać problemy coin-change-II (zliczanie sposobów) i cięcia pręta za pomocą tego wariantu
Plecak bez ograniczeń i Coin Change II to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DSA Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Koncepcja plecaka nieograniczonego
W problemie Unbounded Knapsack każdy przedmiot można wybrać dowolną liczbę razy (w przeciwieństwie do problemu plecakowego 0/1, w którym każdy przedmiot jest używany najwyżej raz). Definicja stanu pozostaje taka sama — dp[c] = maksymalna wartość osiągalna przy pojemności c — zmienia się jednak kierunek iterowania. Ponieważ przedmioty można ponownie wykorzystywać, podczas aktualizowania dp[c] chcemy dopuścić ponowne użycie bieżącego przedmiotu, dlatego iterujemy pojemność od lewej do prawej (w przód).
Iterowanie w przód umożliwia ponowne użycie
Przypomnijmy, że w problemie plecakowym 0/1 iterowaliśmy od prawej do lewej, aby zapobiec ponownemu użyciu przedmiotów. W problemie plecakowym nieograniczonym robimy odwrotnie: iterujemy od lewej do prawej. Podczas obliczania dp[c] wartość dp[c-w] została już zaktualizowana w bieżącym przebiegu — oznacza to, że przedmiot i mógł już zostać uwzględniony. Tego właśnie potrzebujemy: przedmiot i można dodać ponownie do rozwiązania, które już go zawiera.
def unbounded_knapsack(weights, values, W):
dp = [0] * (W + 1)
for i in range(len(weights)):
w, v = weights[i], values[i]
for c in range(w, W + 1): # iterate LEFT TO RIGHT
dp[c] = max(dp[c], dp[c - w] + v)
return dp[W]
weights = [1, 3, 4, 5]
values = [1, 4, 5, 7]
print(unbounded_knapsack(weights, values, 7)) # 9Coin Change II: zliczanie sposobów
Problem Coin Change II polega na tym, aby dla danych nominałów monet i kwoty policzyć liczbę różnych sposobów uzyskania tej kwoty (każdej monety można używać bez ograniczeń). Jest to wariant problemu plecakowego nieograniczonego, w którym zamiast maksymalizować wartość, zliczamy kombinacje. Zdefiniujmy dp[c] jako liczbę sposobów uzyskania kwoty c. Przypadek bazowy: dp[0] = 1 (istnieje jeden sposób uzyskania 0: nic nie wybrać).
Implementacja Coin Change II
Dla każdej monety iterujemy po kwotach od lewej do prawej i dodajemy: dp[c] += dp[c - coin]. Przypadek bazowy dp[0] = 1 inicjuje zliczanie. Zauważmy, że pętla zewnętrzna przebiega po monetach, a wewnętrzna po kwotach — dzięki temu naturalnie otrzymujemy liczbę kombinacji (a nie permutacji), ponieważ każdy nominał jest uwzględniany dokładnie raz w osobnym przebiegu pętli zewnętrznej.
def change(amount, coins):
dp = [0] * (amount + 1)
dp[0] = 1 # one way to make amount 0
for coin in coins:
for c in range(coin, amount + 1):
dp[c] += dp[c - coin]
return dp[amount]
print(change(5, [1, 2, 5])) # 4
print(change(3, [2])) # 0
print(change(10, [10])) # 1Kombinacje a permutacje
Kolejność pętli ma kluczowe znaczenie. Jeśli umieścimy amount w pętli zewnętrznej, a coin w pętli wewnętrznej, będziemy zliczać permutacje (kolejność ma znaczenie). Dla amount=5 i monet [1,2] ciągi 1+2+2 i 2+1+2 zostaną policzone osobno. Jeśli umieścimy coin w pętli zewnętrznej, będziemy zliczać kombinacje (kolejność nie ma znaczenia): 1+2+2 i 2+1+2 są tym samym sposobem. Problem Coin Change II wymaga zliczania kombinacji, dlatego coin znajduje się w pętli zewnętrznej.
# Count COMBINATIONS (order does not matter) — coin outer loop
def combinations(amount, coins):
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins: # coin outer
for c in range(coin, amount + 1):
dp[c] += dp[c - coin]
return dp[amount]
# Count PERMUTATIONS (order matters) — amount outer loop
def permutations(amount, coins):
dp = [0] * (amount + 1)
dp[0] = 1
for c in range(1, amount + 1): # amount outer
for coin in coins:
if c >= coin:
dp[c] += dp[c - coin]
return dp[amount]
print(combinations(5, [1,2,5])) # 4
print(permutations(5, [1,2,5])) # 13Problem cięcia pręta
Inny klasyczny problem plecakowy nieograniczony: mając pręt o długości n i ceny dla każdej długości pręta od 1 do n, należy znaleźć maksymalny przychód przez optymalne pocięcie pręta. Każdy kawałek o długości l można sprzedać za price[l], a kawałki mogą być używane wielokrotnie (pręt można podzielić na wiele kawałków o tej samej długości). Problem ten bezpośrednio odpowiada problemowi plecakowemu nieograniczonemu, w którym W = n, a przedmiotami są różne długości cięcia.
def rod_cutting(prices, n):
# prices[i] = price of rod of length i+1
dp = [0] * (n + 1)
for length in range(1, n + 1): # each cut length
price = prices[length - 1]
for c in range(length, n + 1):
dp[c] = max(dp[c], dp[c - length] + price)
return dp[n]
prices = [1, 5, 8, 9, 10, 17, 17, 20]
print(rod_cutting(prices, 8)) # 22Coin Change I: minimalna liczba monet
Coin Change I (inny problem) polega na znalezieniu minimalnej liczby monet potrzebnych do uzyskania docelowej kwoty. W tym przypadku dp[c] = minimalna liczba monet potrzebnych do uzyskania kwoty c. Zależność rekurencyjna: dp[c] = min(dp[c], dp[c - coin] + 1). Wszystkie wpisy należy zainicjalizować wartością inf, z wyjątkiem dp[0] = 0. Jest to również problem nieograniczony (monet można używać wielokrotnie), dlatego iterujemy od lewej do prawej. Zwracamy dp[amount], jeśli wartość jest skończona, w przeciwnym razie -1.
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for c in range(coin, amount + 1):
dp[c] = min(dp[c], dp[c - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
print(coinChange([1,5,6,9], 11)) # 2 (5+6 or other combos)
print(coinChange([2], 3)) # -1Kluczowa różnica: maksimum, minimum czy zliczanie
Trzy warianty problemu plecakowego nieograniczonego używają różnych operacji na dp[c-coin]: Maksymalizacja wartości: dp[c] = max(dp[c], dp[c-w] + v); inicjalizacja wartością 0. Minimalizacja kosztu: dp[c] = min(dp[c], dp[c-coin] + 1); inicjalizacja wartością inf, dp[0]=0. Zliczanie sposobów: dp[c] += dp[c-coin]; inicjalizacja wartością 0, dp[0]=1. Rozpoznanie właściwego wariantu to połowa sukcesu w zadaniach rekrutacyjnych.
Złożoność i wskazówki rekrutacyjne
Wszystkie warianty problemu plecakowego nieograniczonego działają w czasie O(n × W) i zajmują O(W) pamięci, gdzie n oznacza liczbę typów przedmiotów, a W — docelową kwotę. W problemach monetowych n oznacza liczbę nominałów. Podczas rozmowy rekrutacyjnej należy określić wariant (maksimum/minimum/zliczanie), zapisać DP 1D oraz jasno wskazać, czy pętla zewnętrzna przebiega po monetach, czy po kwocie — osoby oceniające wiedzą, że to rozróżnienie sprawdza dogłębne rozumienie DP.
Rozpoznawanie problemu nieograniczonego i 0/1
Skorzystaj z poniższych wskazówek, aby rozpoznać właściwy wariant: nieograniczone ponowne użycie → problem nieograniczony (iterowanie w przód); każdy przedmiot dokładnie raz → problem 0/1 (iterowanie wstecz); sformułowania „dowolną liczbę razy”, „nieograniczony zapas” lub „dozwolone ponowne użycie” → problem nieograniczony. Przykłady: coin change, rod cutting i integer break — wszystkie są problemami nieograniczonymi. Subset sum, partition i 0/1 knapsack — to problemy 0/1. Pomyłka na tym etapie prowadzi do błędnych odpowiedzi, które trudno zdebugować.
Integer Break i inne warianty
Integer Break (LeetCode 343): podzielić liczbę n na co najmniej 2 dodatnie liczby całkowite tak, aby zmaksymalizować ich iloczyn. Jest to problem plecakowy nieograniczony, w którym „przedmiotami” są liczby całkowite od 2 do n-1. Zdefiniujmy dp[i] jako maksymalny iloczyn liczb sumujących się do i. Dla każdego przedmiotu j od 2 do i dp[i] = max(dp[i], max(j, dp[j]) * max(i-j, dp[i-j])). Pokazuje to, jak wzorzec problemu nieograniczonego uogólnia się poza kontekstem monet.
def integerBreak(n):
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max(j, dp[j]) * max(i-j, dp[i-j]))
return dp[n]
print(integerBreak(10)) # 36 (3+3+4 = 3*3*4 = 36)Szybki test
Proszę sprawdzić swoją wiedzę na temat zagadnień z kursu Data Structures & Algorithms — Coding Interview Prep omówionych w tej lekcji.
Podsumowanie lekcji
W tej lekcji poznali Państwo: że w problemie plecakowym nieograniczonym iteruje się pojemność od lewej do prawej, aby umożliwić ponowne użycie przedmiotu, że Coin Change II zlicza kombinacje, umieszczając coin w pętli zewnętrznej oraz że trzy warianty — maksymalizacja, minimalizacja i zliczanie — różnią się wyłącznie operacją DP i inicjalizacją. Następnie wykorzystamy problem plecakowy 0/1 do rozwiązania Partition Equal Subset Sum.
Często zadawane pytania
Czy lekcja „Plecak bez ograniczeń i Coin Change II” jest bezpłatna?
Tak — pełny tekst „Plecak bez ograniczeń i Coin Change II” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DSA Interview Prep, przejdź na CoddyKit PRO. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Plecak bez ograniczeń i Coin Change II”?
Zezwalać na wielokrotne użycie elementów przez iterowanie pojemności w przód oraz rozwiązywać problemy coin-change-II (zliczanie sposobów) i cięcia pręta za pomocą tego wariantu Ćwiczysz DSA Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DSA Interview Prep?
Nie wymagamy żadnego doświadczenia. DSA Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Plecak bez ograniczeń i Coin Change II”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DSA Interview Prep?
Tak. Każda lekcja DSA Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Plecak 0/1 i optymalizacja pamięci
- Plecak bez ograniczeń i Coin Change II
- Podział na równe sumy podzbiorów
- Suma docelowa ze znakami dodatnimi i ujemnymi