Big-O記法をゼロから学ぶ
漸近的な増加に注目する理由、定数や低次の項を省く方法、Big-Oをひと目で読み取る方法を理解します。
「Big-O記法をゼロから学ぶ」はCoddyKit上の無料Coding Interview Prepレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCoding Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Coding Interview Prepコースには全4レッスンが含まれています。
アルゴリズムの効率を測る理由
2つのプログラムがどちらも正しくても、一方は一瞬で終わり、もう一方は何時間も実行されることがあります。時間計算量は、入力が大きくなったときに実行時間がどのように増加するかを表します。
# 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])) # 9Big-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!)です。これらを知っていれば、1行もコードを書く前に適切なアプローチを選べます。
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)です。定数はアルゴリズムではなくハードウェアに依存します。Big-Oでは定数を省くことで、同じ基準でスケーリングを比較できます。
# 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最良ケース、平均ケース、最悪ケース
Big-Oは最悪ケースを表し、Omegaは最良ケースを、Thetaは両方に対するタイトな上界を表します。面接で「計算量」と聞かれた場合、ほとんどの場合は最悪ケースを意味します。
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)) # -1O(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]償却計算量
償却解析では、多数の操作にわたってコストを平均します。Pythonのappendは償却O(1)です。通常は瞬時に完了し、まれに発生するO(n)のサイズ変更コストも、すべての追加操作に分散されるためです。
# 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')コードから計算量を見抜く
簡単な原則は、ループを数えることです。ループが1つならO(n)、入れ子になったループが2つならO(n^2)、半分ずつ縮小するループならO(log n)です。独立した処理は加算し、入れ子になったループだけを乗算します。コードを確認してください。
# 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]クイックチェック
クイックチェックです。Big-Oと計算量クラスについて、どれだけ理解できたか確認しましょう。1問だけです。きっとできます。🎯
レッスンの振り返り
振り返り: Big-Oは定数を省いた最悪ケースの増加率です。O(1)からO(n!)までのクラスを理解し、独立したループは加算し、入れ子になったループは乗算します。
よくある質問
「Big-O記法をゼロから学ぶ」レッスンは無料ですか?
はい。「Big-O記法をゼロから学ぶ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Coding Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Coding Interview Prepコースには全4レッスンが含まれています。
「Big-O記法をゼロから学ぶ」で何を学びますか?
漸近的な増加に注目する理由、定数や低次の項を省く方法、Big-Oをひと目で読み取る方法を理解します。 ブラウザで直接実行するハンズオンコードでCoding Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Coding Interview Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのCoding Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「Big-O記法をゼロから学ぶ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このCoding Interview Prepレッスンでコードを書いて実行できますか?
はい。すべてのCoding Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Big-O記法をゼロから学ぶ
- ループとネストしたループを分析する
- 再帰と再帰木法
- 空間計算量とトレードオフ