0Pricing
DSA Interview Prep · レッスン

ループとネストしたループを分析する

単一ループ、ネストしたループ、二分探索や三角形状の反復のように範囲が縮小するループの時間計算量を求めます。

「ループとネストしたループを分析する」はCoddyKit上の無料DSA Interview Prepレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDSA Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DSA Interview Prepコースには全4レッスンが含まれています。

単一ループ: O(n)

最も単純なループは本体をn回実行するため、O(n)です。ステップ幅を大きくすると回数は変わりますが、計算量クラスは変わりません。まず、本体が何回実行されるかを数えてください。コードを確認してください。

# O(n): body runs n times
def count_ops_linear(n):
    ops = 0
    for i in range(n):
        ops += 1     # constant work
    return ops

print(count_ops_linear(100))  # 100

# Still O(n): step=2 halves count but same class
def count_ops_half(n):
    ops = 0
    for i in range(0, n, 2):
        ops += 1
    return ops

print(count_ops_half(100))    # 50  => O(n)

入れ子のループ: O(n²)とそれ以上

それぞれn回実行されるループを2つ入れ子にすると、n x n = O(n^2)になります。3つならO(n^3)です。ただし、内側のループの実行回数が一定なら、全体は線形のままです。

def count_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(n):      # n iterations each
            ops += 1
    return ops

print(count_pairs(10))   # 100 = 10^2
print(count_pairs(100))  # 10000 = 100^2
# Doubling n quadruples ops: classic O(n^2)

三角形ループ: O(n²/2) = O(n²)

内側のループがi+1から始まる場合、反復回数は三角形の形になり、n(n-1)/2です。半分という定数を省けば、やはりO(n^2)です。すべての一意な組み合わせを扱う問題は、この形になります。

def count_unique_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(i+1, n): # n-1, n-2, ..., 0
            ops += 1
    return ops

print(count_unique_pairs(10))  # 45 = 10*9/2
print(count_unique_pairs(100)) # 4950
# Still O(n^2) -- constant factor 1/2 dropped

範囲が縮小するループ: O(log n)

各ステップでループ変数を半分にすると、O(log n)になります。重要なのは、範囲が乗法的に縮小しているのか(log n)、加法的に縮小しているのか(n)という点です。コードを確認してください。

def count_log_ops(n):
    ops = 0
    i = n
    while i >= 1:
        ops += 1
        i //= 2   # halve each iteration
    return ops

import math
for n in [8, 16, 64, 1024]:
    ops = count_log_ops(n)
    print(f'n={n}, ops={ops}, log2={int(math.log2(n))}')
# ops tracks log2(n) closely

内側のループが縮小する入れ子ループ: O(n log n)

n回実行される外側のループと、O(log n)の内側のループを組み合わせると、O(n log n)になります。これはマージソートの形です。内側にあるO(log n)の処理を見抜くことが、ソートを分析する鍵になります。

import math

def count_n_log_n(n):
    ops = 0
    for i in range(n):    # n iterations
        j = n
        while j >= 1:     # log n iterations
            ops += 1
            j //= 2
    return ops

for n in [8, 32, 128]:
    ops = count_n_log_n(n)
    predicted = int(n * math.log2(n))
    print(f'n={n}: actual={ops}, n*log2(n)~={predicted}')

依存関係のある内側のループ

内側のループの範囲が外側のインデックスに依存する場合は、1ステップごとではなく、反復の合計回数を数えます。内側のループが0..iまで実行されると、n(n-1)/2 = O(n^2)になります。コードを確認してください。

# Inner loop runs i times: total = 0+1+2+...+(n-1) = n(n-1)/2 => O(n^2)
def sum_inner_i(n):
    ops = 0
    for i in range(n):
        for j in range(i):   # runs 0,1,2,...,n-1 times
            ops += 1
    return ops

print(sum_inner_i(10))  # 45 = 10*9/2  => O(n^2)

# Inner loop runs n/i times (i doubles): sum ≈ n*log n => O(n log n)
def sum_inner_n_over_i(n):
    ops = 0
    i = 1
    while i <= n:
        for j in range(n // i):
            ops += 1
        i *= 2
    return ops
print(sum_inner_n_over_i(64))  # ~ 64*6 = 384

バブルソートをステップごとに分析する

バブルソートはn(n-1)/2回比較するため、O(n^2)です。早期終了を取り入れても、逆順にソートされた入力ではすべての比較が必要になります。大きな入力に対しては遅すぎます。

def bubble_sort(arr):
    n = len(arr)
    comparisons = 0
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            comparisons += 1
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                swapped = True
        if not swapped:  # early exit if sorted
            break
    return comparisons

arr = list(range(10, 0, -1))  # worst case: reversed
ops = bubble_sort(arr)
print(f'Sorted: {arr}')
print(f'Comparisons: {ops}')  # 45 = 10*9/2

文字列と部分文字列に対するループ

注意してください。PythonのスライスはO(k)であり、無料ではありません。また、ループ内で+によって文字列を連結すると、毎回コピーが発生するためO(n^2)になります。代わりに''.join(parts)を使用してください。コードを確認してください。

# O(n^2): string concat in loop
def build_bad(n):
    s = ''
    for i in range(n):
        s += str(i)  # copies s each time!
    return s

# O(n): join is a single pass
def build_good(n):
    parts = []
    for i in range(n):
        parts.append(str(i))
    return ''.join(parts)

print(build_good(10))  # '0123456789'

複数の入力パラメータ

入力が2つある場合、計算量は両方を使って表すことがあります。別々の処理ならO(m + n)、入れ子になった処理ならO(m x n)です。グラフではO(V + E)と表すことがよくあります。各変数には分かりやすい名前を付けてください。

# O(m + n): two independent loops
def independent(m, n):
    a = sum(range(m))  # O(m)
    b = sum(range(n))  # O(n)
    return a + b       # total O(m + n)

# O(m * n): nested
def nested(m, n):
    count = 0
    for i in range(m):     # O(m)
        for j in range(n): # O(n) each
            count += 1
    return count  # O(m * n)

print(independent(5, 10))  # 10 + 45 = 55
print(nested(5, 10))       # 50

ループ内のループと逐次的な呼び出し

関数呼び出しは無料ではありません。関数の内部にあるループも計算に含めます。O(n)のヘルパー関数をn回呼び出すと、O(n^2)になります。分析するときは、ブラックボックスの呼び出しの内部も必ず確認してください。

# Naive string matching: O(n*m)
def naive_search(text, pattern):
    n, m = len(text), len(pattern)
    matches = []
    for i in range(n - m + 1):  # O(n)
        if text[i:i+m] == pattern:  # O(m) comparison + O(m) slice
            matches.append(i)
    return matches
# Total: O(n*m)

print(naive_search('abcabcabc', 'abc'))  # [0, 3, 6]

実践: 計算量を一目で見抜く

習慣にしましょう。ループの入れ子の深さを数え、内側のループが外側のループに依存しているかを確認し、関数呼び出しやスライスに隠れたコストがないか注意してください。コードは実際に試せるパズルです。

# What is the complexity of this function?
def mystery(nums):
    result = []
    for i in range(len(nums)):          # O(n)
        for j in range(i, len(nums)):   # O(n) worst
            if sum(nums[i:j+1]) == 0:   # O(n) slice + sum!
                result.append((i, j))
    return result
# Answer: O(n^3)  -- three nested n-proportional ops
# Outer O(n) x inner O(n) x sum/slice O(n) = O(n^3)

クイックチェック

クイックチェックです。ループ分析のコツがどれだけ身に付いたか確認しましょう。ここでは自分の推論を信じてください。💪

レッスンの振り返り

振り返り: 入れ子になったループは乗算し、独立したループは加算します。内側のループが半分ずつ縮小する場合はO(n log n)になり、呼び出しやスライスの内部にある隠れたコストも数える必要があります。

よくある質問

「ループとネストしたループを分析する」レッスンは無料ですか?

はい。「ループとネストしたループを分析する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DSA Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DSA Interview Prepコースには全4レッスンが含まれています。

「ループとネストしたループを分析する」で何を学びますか?

単一ループ、ネストしたループ、二分探索や三角形状の反復のように範囲が縮小するループの時間計算量を求めます。 ブラウザで直接実行するハンズオンコードでDSA Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

DSA Interview Prepを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのDSA Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「ループとネストしたループを分析する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このDSA Interview Prepレッスンでコードを書いて実行できますか?

はい。すべてのDSA Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Big-O記法をゼロから学ぶ
  2. ループとネストしたループを分析する
  3. 再帰と再帰木法
  4. 空間計算量とトレードオフ
← DSA Interview Prepに戻る