0Pricing
Coding Interview Prep · บทเรียน

วิเคราะห์ลูปและลูปซ้อน

คำนวณความซับซ้อนด้านเวลาของลูปเดี่ยว ลูปซ้อน และลูปที่ช่วงลดลง เช่น การค้นหาแบบทวิภาคหรือการวนซ้ำแบบสามเหลี่ยม

วิเคราะห์ลูปและลูปซ้อน เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding 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 ครั้ง จะได้ n x n = O(n^2) ส่วนสามชั้นจะได้ 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) หลังจากตัดตัวหาร 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}')

ลูปด้านในที่ขึ้นอยู่กับลูปด้านนอก

เมื่อช่วงของลูปด้านในขึ้นอยู่กับดัชนีของลูปด้านนอก ให้ นับจำนวนรอบ ทั้งหมด ไม่ใช่นับแยกตามแต่ละขั้น ลูปด้านในที่ทำงานตั้งแต่ 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'

พารามิเตอร์ข้อมูลเข้าหลายตัว

เมื่อมีข้อมูลเข้าสองชุด ความซับซ้อนอาจใช้ทั้งสองตัวแปร: 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) และต้องนับค่าใช้จ่ายแฝงภายในการเรียกฟังก์ชันและการตัดแบ่งด้วย

คำถามที่พบบ่อย

บทเรียน “วิเคราะห์ลูปและลูปซ้อน” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “วิเคราะห์ลูปและลูปซ้อน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “วิเคราะห์ลูปและลูปซ้อน”

คำนวณความซับซ้อนด้านเวลาของลูปเดี่ยว ลูปซ้อน และลูปที่ช่วงลดลง เช่น การค้นหาแบบทวิภาคหรือการวนซ้ำแบบสามเหลี่ยม คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “วิเคราะห์ลูปและลูปซ้อน” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. สัญกรณ์ Big-O ตั้งแต่พื้นฐาน
  2. วิเคราะห์ลูปและลูปซ้อน
  3. การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ
  4. ความซับซ้อนด้านพื้นที่และการแลกเปลี่ยน
← กลับไปที่ Coding Interview Prep