0Pricing
DSA Interview Prep · Lekcja

Złożoność pamięciowa i kompromisy

Zmierz ą Państwo pamięć pomocniczą stosu wywołań i pomocniczych struktur danych oraz poznają kompromisy między czasem a pamięcią w memoizacji i algorytmach działających w miejscu.

Złożoność pamięciowa i kompromisy to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 4 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.

Co mierzy złożoność pamięciowa?

Złożoność pamięciowa mierzy dodatkową pamięć poza danymi wejściowymi, nazywaną pamięcią pomocniczą. Kilka zmiennych oznacza O(1), a tablica wyników lub mapa haszująca — O(n). Zobacz kod.

# O(1) auxiliary space
def sum_array(nums):
    total = 0       # one integer variable
    for n in nums:
        total += n  # constant extra space
    return total

# O(n) auxiliary space
def copy_array(nums):
    return list(nums)  # allocates n slots

print(sum_array([1, 2, 3, 4]))  # 10
print(copy_array([1, 2, 3, 4]))  # [1, 2, 3, 4]

Pamięć stosu wywołań w rekurencji

Każde wywołanie rekurencyjne dodaje ramkę stosu, więc o wymaganej pamięci decyduje głębokość. Rekurencja liniowa ma złożoność O(n), a przechodzenie DFS zrównoważonego drzewa — O(log n). Wersja iteracyjna może zapewnić lepszą kontrolę nad tym kosztem.

import sys

def recursive_sum(n):
    if n == 0: return 0
    return n + recursive_sum(n - 1)
# Space: O(n) stack frames

def iterative_sum(n):
    total = 0
    while n > 0:
        total += n
        n -= 1
    return total
# Space: O(1)

print(recursive_sum(100))   # 5050
print(iterative_sum(100))   # 5050

Pamięć sortowania przez scalanie: O(n)

Sortowanie przez scalanie wymaga O(n) dodatkowej pamięci na tablice tymczasowe. To cena za stabilne sortowanie o złożoności O(n log n) — sortowanie kopcowe oszczędza pamięć, ale nie jest stabilne. Zobacz kod.

import tracemalloc

tracemalloc.start()

def merge_sort(arr):
    if len(arr) <= 1: return arr
    m = len(arr) // 2
    l = merge_sort(arr[:m])    # new list
    r = merge_sort(arr[m:])    # new list
    out, i, j = [], 0, 0
    while i < len(l) and j < len(r):
        if l[i] <= r[j]: out.append(l[i]); i+=1
        else:             out.append(r[j]); j+=1
    return out + l[i:] + r[j:]

data = list(range(1000, 0, -1))
merge_sort(data)
_, peak = tracemalloc.get_traced_memory()
print(f'Peak memory: {peak} bytes')  # proportional to n

Algorytmy działające w miejscu: pamięć O(1)

Algorytm działający w miejscu modyfikuje dane wejściowe bez dodatkowej pamięci proporcjonalnej do ich rozmiaru — na przykład odwraca tablicę za pomocą dwóch wskaźników. Dzięki temu jego złożoność pamięciowa wynosi O(1). Zobacz kod.

def reverse_inplace(arr):
    l, r = 0, len(arr) - 1
    while l < r:
        arr[l], arr[r] = arr[r], arr[l]  # swap
        l += 1
        r -= 1
    # Space: O(1) -- only two pointer variables

def rotate_right(arr, k):
    '''Rotate array right by k positions in-place.'''
    n = len(arr)
    k %= n
    arr.reverse()          # O(1) space
    arr[:k] = arr[:k][::-1]
    arr[k:]  = arr[k:][::-1]

a = [1, 2, 3, 4, 5]
rotate_right(a, 2)
print(a)  # [4, 5, 1, 2, 3]

Kompromis czas–pamięć: Two Sum

Kompromis między czasem a pamięcią występuje wszędzie. Problem Two Sum można rozwiązać w czasie O(n^2) i przy użyciu O(1) pamięci albo w czasie O(n) i przy użyciu O(n) pamięci za pomocą mapy haszującej. Należy podać obie wartości i zapytać, co jest ważniejsze.

# O(n^2) time, O(1) space
def two_sum_slow(nums, target):
    for i in range(len(nums)):          # O(n)
        for j in range(i+1, len(nums)): # O(n)
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

# O(n) time, O(n) space
def two_sum_fast(nums, target):
    seen = {}                    # O(n) space
    for i, n in enumerate(nums):
        comp = target - n
        if comp in seen:         # O(1) lookup
            return [seen[comp], i]
        seen[n] = i
    return []

print(two_sum_fast([2, 7, 11, 15], 9))  # [0, 1]

Pamięć: memoizacja a tabulacja

Podejście od góry w dół z memoizacją kosztuje O(n) na pamięć memo oraz O(n) na stos; podejście od dołu z tabulacją pomija stos. Przechowywanie tylko kilku ostatnich wierszy zmniejsza koszt do O(1) — to programowanie dynamiczne zoptymalizowane pod kątem pamięci.

# Fibonacci: O(n) space with full table
def fib_table(n):
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

# O(1) space: keep only last two values
def fib_optimal(n):
    if n <= 1: return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

print(fib_table(10))    # 55
print(fib_optimal(10))  # 55

Pamięć mapy haszującej: O(n)

Mapa haszująca jest typowym źródłem kosztu O(n) pamięci w rozwiązaniach: zbiór odwiedzonych elementów służy do śledzenia odwiedzin, a mapa częstotliwości — do zliczania. Zawsze należy to podawać — „czas O(n), pamięć O(n)” to pełna odpowiedź.

def contains_duplicate(nums):
    # O(n) time, O(n) space
    seen = set()
    for n in nums:
        if n in seen: return True
        seen.add(n)
    return False

def group_anagrams(words):
    # O(n*m) time, O(n) space  (m = avg word length)
    from collections import defaultdict
    groups = defaultdict(list)
    for w in words:
        groups[tuple(sorted(w))].append(w)
    return list(groups.values())

print(contains_duplicate([1,2,3,1]))  # True
print(group_anagrams(['eat','tea','tan','ate','nat','bat']))

Analiza pamięciowa algorytmów grafowych

Grafy wymagają rzeczywistej pamięci: lista sąsiedztwa ma złożoność O(V + E), zbiór odwiedzonych w BFS i kolejka mają złożoność O(V), a rekurencja DFS może osiągnąć głębokość O(V). Złożoność pamięciową grafu należy podawać za pomocą V i E.

from collections import deque

def bfs(graph, start):
    # Space: O(V) for visited set + O(V) for queue
    visited = set()      # O(V)
    queue = deque([start])  # O(V) max
    order = []
    while queue:
        node = queue.popleft()
        if node in visited: continue
        visited.add(node)
        order.append(node)
        for nb in graph.get(node, []):
            queue.append(nb)
    return order

g = {0:[1,2], 1:[3], 2:[3], 3:[]}
print(bfs(g, 0))  # [0, 1, 2, 3]

Pułapki alokowania napisów i tablic

Ukryte alokacje mogą zwiększyć zużycie pamięci do O(n): operacja slicing tworzy nową listę, a użycie + na napisach w pętli ma złożoność O(n^2). sorted() tworzy kopię, ale lst.sort() działa w miejscu. Zobacz kod.

# Hidden allocations:
nums = [1, 2, 3, 4, 5]

# Creates a NEW list -- O(n) space
slice_copy = nums[1:4]  # [2, 3, 4]

# Creates a NEW sorted list -- O(n) space
sorted_copy = sorted(nums)  # nums unchanged

# Sorts IN PLACE -- O(1) extra space
nums.sort()

print(slice_copy)   # [2, 3, 4]
print(sorted_copy)  # [1, 2, 3, 4, 5]
print(nums)         # [1, 2, 3, 4, 5]

Rozpoznawanie kompromisów pamięciowych podczas rozmów kwalifikacyjnych

Złożoność pamięciową należy podawać od razu. Jeśli osoba przeprowadzająca rozmowę oczekuje mniejszego zużycia pamięci, często stosuje się programowanie dynamiczne od dołu zamiast memoizacji albo sortowanie w miejscu zamiast mapy haszującej. Zobacz kod.

# Problem: find if array has duplicates
# Option 1: O(1) time-per-check, O(n) space
def has_dup_hash(nums):
    return len(nums) != len(set(nums))

# Option 2: O(n log n) time, O(1) extra space
def has_dup_sort(nums):
    nums_copy = sorted(nums)  # O(n) space -- still!
    for i in range(1, len(nums_copy)):
        if nums_copy[i] == nums_copy[i-1]:
            return True
    return False

# Option 3: truly O(1) extra -- sort in-place
def has_dup_inplace(nums):
    nums.sort()               # modifies original
    for i in range(1, len(nums)):
        if nums[i] == nums[i-1]: return True
    return False

Szablon pełnego opisu złożoności

Zawsze należy podawać pełny opis — czas i pamięć: „czas O(n), dodatkowa pamięć O(1)”. Jeśli występują kompromisy, warto o nich wspomnieć. To właśnie wyróżnia bardziej doświadczonych kandydatów.

# Complete complexity example: Merge Intervals
def merge_intervals(intervals):
    # Time: O(n log n) for sort + O(n) for merge = O(n log n)
    # Space: O(n) for output (could be n/2 to n intervals)
    intervals.sort(key=lambda x: x[0])  # O(n log n)
    merged = [intervals[0]]
    for start, end in intervals[1:]:
        if start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

print(merge_intervals([[1,3],[2,6],[8,10],[15,18]]))
# [[1,6],[8,10],[15,18]]

Szybki test

Szybki test — sprawdźmy, jak zostały przyswojone zagadnienia złożoności pamięciowej. Są Państwo na to gotowi. ✅

Podsumowanie lekcji

Podsumowanie: pamięć pomocniczą liczy się niezależnie od danych wejściowych, rekurencja wykorzystuje O(głębokości) pamięci stosu, a kompromis między czasem a pamięcią wpływa na większość decyzji projektowych dotyczących algorytmów.

Często zadawane pytania

Czy lekcja „Złożoność pamięciowa i kompromisy” jest bezpłatna?

Tak — pełny tekst „Złożoność pamięciowa i kompromisy” 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 „Złożoność pamięciowa i kompromisy”?

Zmierz ą Państwo pamięć pomocniczą stosu wywołań i pomocniczych struktur danych oraz poznają kompromisy między czasem a pamięcią w memoizacji i algorytmach działających w miejscu. Ć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 4 z 4.

Ile czasu zajmuje lekcja „Złożoność pamięciowa i kompromisy”?

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

  1. Notacja Big-O od podstaw
  2. Analiza pętli i pętli zagnieżdżonych
  3. Rekurencja i metoda drzewa rekurencji
  4. Złożoność pamięciowa i kompromisy
← Powrót do DSA Interview Prep