0Pricing
DSA Interview Prep · 강의

Big-O 표기법 기초

점근적 증가율을 살펴보는 이유, 상수와 저차항을 생략하는 방법, Big-O를 한눈에 읽는 방법을 이해합니다.

Big-O 표기법 기초은(는) CoddyKit의 무료 DSA Interview Prep 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DSA Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.

알고리즘 효율성을 측정하는 이유

두 프로그램이 모두 올바르더라도 하나는 눈 깜짝할 사이에 끝나고 다른 하나는 몇 시간 동안 실행될 수 있습니다. 시간 복잡도는 입력이 커질 때 실행 시간이 어떻게 증가하는지 설명합니다.

# O(n) approach
def find_max_linear(nums):
    m = nums[0]
    for n in nums:
        if n > m: m = n
    return m

# O(n^2) approach (unnecessary double loop)
def find_max_quadratic(nums):
    for i in range(len(nums)):
        is_max = all(nums[i] >= nums[j] for j in range(len(nums)))
        if is_max: return nums[i]

print(find_max_linear([3, 1, 4, 1, 5, 9]))  # 9

Big-O: 점근적 상한

Big-O는 비용이 증가하는 속도의 최악의 경우 상한을 설명합니다. 상수와 더 작은 항을 제거하는 것이 핵심입니다. 규모가 커지면 지배적인 항만 중요하기 때문입니다. 코드를 확인해 보십시오.

# T(n) = 3n^2 + 5n + 100 is O(n^2)
# because the n^2 term dominates for large n

# T(n) = 2n + 1000 is O(n)
# the constant 1000 becomes negligible

# Rule: drop constants and lower-order terms
# 5n^3 + 2n^2 + n + 1  =>  O(n^3)
# 100 * log(n) + n      =>  O(n)
print('O(n^2) example: counting iterations')
n = 1000
count = sum(1 for i in range(n) for j in range(n))
print(count)  # 1_000_000 = n^2

일반적인 복잡도 유형

가장 빠른 것부터 가장 느린 것까지 다음과 같습니다. O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n), O(n!). 이러한 유형을 알면 코드를 한 줄도 작성하기 전에 적절한 접근법을 선택할 수 있습니다.

import math

n = 1000
print(f'O(1):       {1}')
print(f'O(log n):   {int(math.log2(n))}')
print(f'O(n):       {n}')
print(f'O(n log n): {int(n * math.log2(n))}')
print(f'O(n^2):     {n**2}')
# O(2^n) for n=1000 is astronomically large
# O(n!) even larger

상수를 생략하는 이유

5n단계를 실행하든 2n단계를 실행하든 둘 다 O(n)입니다. 상수는 알고리즘이 아니라 하드웨어에 따라 달라집니다. 빅오는 상수를 제외하므로 같은 기준에서 증가 양상을 비교할 수 있습니다.

# Both are O(n) — different constants
def count_a(n):
    total = 0
    for i in range(n):   # n ops
        total += 1
    for i in range(n):   # n ops
        total += 1
    return total  # T(n) = 2n  =>  O(n)

def count_b(n):
    total = 0
    for i in range(5 * n):  # 5n ops
        total += 1
    return total  # T(n) = 5n  =>  O(n)

print(count_a(10), count_b(10))  # 20 50

최선, 평균, 최악의 경우

빅오는 최악의 경우를, 오메가는 최선의 경우를, 세타는 둘 모두에 대한 정확한 경계를 나타냅니다. 면접관이 "복잡도"를 물으면 거의 항상 최악의 경우를 의미합니다.

def linear_search(nums, target):
    for i, n in enumerate(nums):
        if n == target:
            return i  # best case: target at index 0 => O(1)
    return -1         # worst case: not found => O(n)

# Best case O(1): target is first element
print(linear_search([5,1,2,3], 5))   # 0

# Worst case O(n): target not in list
print(linear_search([1,2,3,4], 9))   # -1

O(log n): 검색 공간 절반으로 줄이기

알고리즘이 각 단계에서 입력을 절반으로 줄이면 O(log n)입니다. 이진 검색처럼 동작하는 경우입니다. 항목이 10억 개여도 약 30단계만 필요하므로 매우 빠릅니다. 코드를 확인해 보세요.

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    steps = 0
    while lo <= hi:
        steps += 1
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid, steps
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1, steps

import math
arr = list(range(1000))
idx, s = binary_search(arr, 999)
print(f'Found at {idx} in {s} steps (log2(1000)~={math.log2(1000):.1f})')

O(n log n): 정렬의 하한

비교 정렬은 최악의 경우 최소한 O(n log n)이 필요합니다. 이는 실제 수학적 하한입니다. 따라서 정렬 후 스캔의 전체 복잡도는 O(n^2)이 아니라 O(n log n)입니다. 코드에서 병합 정렬을 보여 줍니다.

# Merge sort: O(n log n)
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left  = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(a, b):
    res, i, j = [], 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]: res.append(a[i]); i+=1
        else:             res.append(b[j]); j+=1
    return res + a[i:] + b[j:]

print(merge_sort([5,2,8,1,9,3]))  # [1,2,3,5,8,9]

상각 복잡도

상각 분석은 여러 연산에 걸쳐 비용의 평균을 계산합니다. 파이썬의 append는 상각 기준으로 O(1)입니다. 대부분은 즉시 끝나고, 드물게 발생하는 O(n) 크기 조정 비용이 모든 append에 걸쳐 분산되기 때문입니다.

# Dynamic array append is O(1) amortised
import sys

lst = []
capacities = []
for i in range(16):
    lst.append(i)
    capacities.append(sys.getsizeof(lst))

# Size jumps show reallocation events
for i, c in enumerate(capacities):
    if i > 0 and capacities[i] != capacities[i-1]:
        print(f'Realloc at i={i}, new size={c} bytes')

코드에서 복잡도 알아보기

간단한 규칙은 반복문을 세는 것입니다. 반복문 하나는 O(n), 중첩된 반복문 두 개는 O(n^2), 절반씩 줄어드는 반복문은 O(log n)입니다. 서로 독립적인 순회는 add하고, 중첩된 반복문만 multiply합니다. 코드를 확인해 보세요.

# Two independent passes: O(n) + O(n) = O(n)
def two_passes(nums):
    total = sum(nums)           # O(n)
    mean = total / len(nums)
    diffs = [abs(n - mean) for n in nums]  # O(n)
    return max(diffs)           # O(n)
# Overall: O(n) -- NOT O(n^2)

# Nested loops: O(n) * O(n) = O(n^2)
def all_pairs(nums):
    pairs = []
    for i in range(len(nums)):       # O(n)
        for j in range(i+1, len(nums)): # O(n)
            pairs.append((nums[i], nums[j]))
    return pairs  # O(n^2)

공간 복잡도 기초

공간 복잡도는 입력 자체를 제외하고 추가로 사용하는 메모리를 추적합니다. 제자리 뒤집기는 O(1)이고 해시 맵은 O(n)입니다. 시간과 공간을 맞바꿀 때는 항상 두 복잡도를 모두 밝혀야 합니다.

# O(1) space: reverse in-place
def reverse_inplace(arr):
    l, r = 0, len(arr) - 1
    while l < r:
        arr[l], arr[r] = arr[r], arr[l]
        l += 1; r -= 1

# O(n) space: create reversed copy
def reverse_copy(arr):
    return arr[::-1]

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

면접에서 복잡도 설명하기

질문을 받지 않아도 항상 복잡도를 먼저 말하세요. "시간 복잡도는 O(n log n)이고, 공간 복잡도는 O(n)입니다."라고 한 뒤 더 빠른 방법을 제안하세요. 이런 습관은 진정한 숙련도를 보여 줍니다.

# Example of explaining complexity step by step
def two_sum(nums, target):
    # O(n) time: one pass through nums
    # O(n) space: hash map stores up to n elements
    seen = {}  # value -> index
    for i, n in enumerate(nums):
        complement = target - n
        if complement in seen:   # O(1) lookup
            return [seen[complement], i]
        seen[n] = i
    return []

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

빠른 확인

빠르게 확인해 보겠습니다. 빅오와 복잡도 분류에 대해 무엇을 익혔는지 보여 주세요. 한 문제입니다. 충분히 해낼 수 있습니다. 🎯

학습 내용 복습

복습해 보겠습니다. 빅오는 상수를 제외한 최악의 경우 증가율을 나타냅니다. O(1)부터 O(n!)까지의 분류를 알고 있으며, 독립적인 반복문은 더하고 중첩된 반복문은 곱합니다.

자주 묻는 질문

“Big-O 표기법 기초” 강의는 무료인가요?

네 — “Big-O 표기법 기초” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DSA Interview Prep 강의 전체를 잠금 해제할 수 있습니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.

“Big-O 표기법 기초”에서 뭘 배우나요?

점근적 증가율을 살펴보는 이유, 상수와 저차항을 생략하는 방법, Big-O를 한눈에 읽는 방법을 이해합니다. 브라우저에서 직접 실행하는 실습 코드로 DSA Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

DSA Interview Prep을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 DSA Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“Big-O 표기법 기초” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 DSA Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 DSA Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Big-O 표기법 기초
  2. 반복문과 중첩 반복문 분석
  3. 재귀와 재귀 트리 방법
  4. 공간 복잡도와 트레이드오프
← DSA Interview Prep(으)로 돌아가기