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

DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่

ใช้ DFS นับองค์ประกอบที่เชื่อมต่อ แก้โจทย์จำนวนเกาะบนกริดสองมิติ และสร้างการเติมพื้นที่สำหรับประมวลผลภาพ

DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่ เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

นิยามองค์ประกอบเชื่อมต่อ

องค์ประกอบเชื่อมต่อในกราฟไม่มีทิศทางคือเซตจุดยอดที่ขยายเพิ่มไม่ได้ โดยมีเส้นทางระหว่างจุดยอดทุกคู่ในเซตนั้น กราฟหนึ่งกราฟอาจมีองค์ประกอบที่ไม่เชื่อมต่อกันหลายองค์ประกอบ การค้นหาองค์ประกอบเชื่อมต่อเป็นพื้นฐานของโจทย์กราฟจำนวนมาก ไม่ว่าจะเป็นการจัดกลุ่ม การรวม การนับจำนวนเกาะ และการรวมบัญชี ล้วนลดรูปเป็นแนวคิดพื้นฐานนี้ได้

from collections import defaultdict

# Graph with 3 components: {0,1,2}, {3,4}, {5}
graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,2),(3,4)]:
    graph[u].append(v)
    graph[v].append(u)
# Node 5 is isolated (no edges)
for node in [0,1,2,3,4,5]:
    if node not in graph:
        graph[node] = []

# We need DFS or BFS from each unvisited node
# to discover all components
print('Graph has nodes 0-5 with components: {0,1,2}, {3,4}, {5}')

การนับองค์ประกอบเชื่อมต่อด้วย DFS

วนดูโหนดทั้งหมด สำหรับโหนดที่ยังไม่เคยเยี่ยมชมแต่ละโหนด ให้เริ่ม DFS เพื่อทำเครื่องหมายโหนดทั้งหมดที่เข้าถึงได้ว่าเยี่ยมชมแล้ว การเริ่ม DFS แต่ละครั้งหมายถึงการค้นพบองค์ประกอบใหม่หนึ่งองค์ประกอบ ให้นับจำนวนครั้งที่เริ่ม DFS เพื่อหาจำนวนองค์ประกอบ อัลกอริทึม O(V + E) นี้ทำงานได้อย่างถูกต้องไม่ว่ากราฟจะเชื่อมต่อกันทั้งหมดหรือไม่

from collections import defaultdict

def count_components(n, edges):
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)

    visited = set()
    count = 0

    def dfs(node):
        visited.add(node)
        for nb in graph[node]:
            if nb not in visited:
                dfs(nb)

    for node in range(n):
        if node not in visited:
            dfs(node)
            count += 1

    return count

print(count_components(6, [(0,1),(0,2),(1,2),(3,4)]))  # 3
print(count_components(5, [(0,1),(1,2),(3,4)]))          # 2

จำนวนเกาะ

จำนวนเกาะ (LeetCode #200) เป็นโจทย์องค์ประกอบเชื่อมต่อแบบมาตรฐานบนตารางสองมิติ เซลล์ «1» แต่ละเซลล์เป็นส่วนหนึ่งของเกาะ และเซลล์ «1» ที่อยู่ติดกัน (ขึ้น/ลง/ซ้าย/ขวา) เป็นเกาะเดียวกัน ให้นับจำนวนเกาะที่แตกต่างกันโดยใช้ DFS: วนดูเซลล์ทั้งหมด และเมื่อพบ «1» ที่ยังไม่เคยเยี่ยมชม ให้เริ่ม DFS เพื่อทำเครื่องหมายเซลล์ «1» ที่เชื่อมต่อกันทั้งหมด (การเติมพื้นที่) แล้วเพิ่มตัวนับ

def num_islands(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if grid[r][c] != '1':
            return
        grid[r][c] = '#'  # mark visited in-place
        dfs(r+1,c); dfs(r-1,c)
        dfs(r,c+1); dfs(r,c-1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                dfs(r, c)
                count += 1
    return count

grid = [['1','1','0','0','0'],
        ['1','1','0','0','0'],
        ['0','0','1','0','0'],
        ['0','0','0','1','1']]
print(num_islands(grid))  # 3

อัลกอริทึมเติมพื้นที่

การเติมพื้นที่ (LeetCode #733) แทนที่สีของเซลล์ที่เชื่อมต่อกันทั้งหมดจากสีเริ่มต้นที่กำหนดด้วยสีใหม่ ซึ่งทำงานเหมือนเครื่องมือถังสีในโปรแกรมแก้ไขรูปภาพ ให้ใช้ DFS โดยเริ่มจากพิกเซลต้นทาง แล้วเปลี่ยนสีโหนดเพื่อนบ้านทั้งหมดที่มีสีตรงกับสีเดิมแบบเรียกซ้ำ กรณีขอบเขตที่สำคัญคือ หากสีของเซลล์เริ่มต้นตรงกับสีใหม่อยู่แล้ว ให้จบการทำงานทันทีเพื่อป้องกันการเรียกซ้ำไม่สิ้นสุด

def flood_fill(image, sr, sc, new_color):
    original = image[sr][sc]
    if original == new_color:
        return image  # edge case: same color, nothing to do
    rows, cols = len(image), len(image[0])

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if image[r][c] != original:
            return
        image[r][c] = new_color
        dfs(r+1,c); dfs(r-1,c)
        dfs(r,c+1); dfs(r,c-1)

    dfs(sr, sc)
    return image

image = [[1,1,1],[1,1,0],[1,0,1]]
result = flood_fill(image, 1, 1, 2)
for row in result: print(row)
# [[2,2,2],[2,2,0],[2,0,1]]

พื้นที่เกาะสูงสุด

พื้นที่เกาะสูงสุด (LeetCode #695) ต่อยอดจากการนับเกาะ โดยสำหรับแต่ละเกาะให้คืนค่าขนาดของเกาะที่ใหญ่ที่สุด ระหว่างการเติมพื้นที่ด้วย DFS ให้คุณนับเซลล์ที่ทำเครื่องหมายไว้ DFS จะคืนค่าขนาดของเกาะปัจจุบัน และคุณจะติดตามค่าสูงสุดจากทุกเกาะ นี่คือการเพิ่มความสามารถอย่างง่ายจากรูปแบบองค์ประกอบที่เชื่อมต่อกัน

def max_area_of_island(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    max_area = 0

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return 0
        if grid[r][c] != 1:
            return 0
        grid[r][c] = 0  # mark visited
        return (1 + dfs(r+1,c) + dfs(r-1,c) +
                dfs(r,c+1) + dfs(r,c-1))

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 1:
                max_area = max(max_area, dfs(r, c))
    return max_area

grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],
        [0,0,0,0,0,0,0,1,1,1,0,0,0],
        [0,1,1,0,1,0,0,0,0,0,0,0,0],
        [0,1,0,0,1,1,0,0,1,0,1,0,0]]
print(max_area_of_island(grid))  # 6

การไหลของน้ำสู่มหาสมุทรแปซิฟิกและแอตแลนติก

การไหลของน้ำสู่มหาสมุทรแปซิฟิกและแอตแลนติก (LeetCode #417) ถามว่าเซลล์ใดบ้างที่น้ำสามารถไหลไปถึงทั้งมหาสมุทรแปซิฟิก (ขอบด้านบน/ด้านซ้าย) และมหาสมุทรแอตแลนติก (ขอบด้านล่าง/ด้านขวา) แทนที่จะจำลองการไหลของน้ำลงด้านล่าง ให้ใช้ DFS แบบย้อนกลับ โดยจำลองว่าน้ำไหล ขึ้น จากมหาสมุทร ทำ DFS สองรอบ รอบหนึ่งเริ่มจากขอบของแปซิฟิก และอีกรอบเริ่มจากขอบของแอตแลนติก เพื่อรวบรวมเซลล์ที่เข้าถึงได้ ส่วนตัดกันคือคำตอบ

def pacific_atlantic(heights):
    rows, cols = len(heights), len(heights[0])
    pac = set(); atl = set()

    def dfs(r, c, visited, prev_h):
        if (r,c) in visited or r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if heights[r][c] < prev_h:
            return  # water can't flow uphill in reverse
        visited.add((r,c))
        for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
            dfs(r+dr, c+dc, visited, heights[r][c])

    for r in range(rows):
        dfs(r, 0, pac, heights[r][0])           # Pacific left
        dfs(r, cols-1, atl, heights[r][cols-1]) # Atlantic right
    for c in range(cols):
        dfs(0, c, pac, heights[0][c])            # Pacific top
        dfs(rows-1, c, atl, heights[rows-1][c]) # Atlantic bottom

    return sorted(pac & atl)  # intersection

print(pacific_atlantic([[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]))

DFS แบบวนซ้ำสำหรับองค์ประกอบที่เชื่อมต่อกัน

ใช้ DFS แบบวนซ้ำ (โดยมีสแตกที่จัดการอย่างชัดเจน) เพื่อหลีกเลี่ยงขีดจำกัดการเรียกซ้ำของภาษาไพธอนบนกริดขนาดใหญ่ เวอร์ชันแบบวนซ้ำให้ผลเทียบเท่ากับ DFS แบบเรียกซ้ำ แต่ใช้สแตกแทนสแตกการเรียก ให้ใส่โหนดเริ่มต้นลงในสแตก จากนั้นนำออก ทำเครื่องหมายว่าเข้าชมแล้ว และใส่โหนดข้างเคียงที่ยังไม่เคยเข้าชมลงไป วิธีนี้รองรับกริดที่มีเซลล์หลายล้านเซลล์ได้อย่างปลอดภัย ในกรณีที่ DFS แบบเรียกซ้ำอาจทำให้สแตกโอเวอร์โฟลว์

def count_components_iterative(n, edges):
    from collections import defaultdict
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)

    visited = set()
    count = 0

    for start in range(n):
        if start in visited:
            continue
        # Iterative DFS
        stack = [start]
        while stack:
            node = stack.pop()
            if node in visited:
                continue
            visited.add(node)
            for nb in graph[node]:
                if nb not in visited:
                    stack.append(nb)
        count += 1

    return count

print(count_components_iterative(6, [(0,1),(0,2),(1,2),(3,4)]))  # 3

ภูมิภาคที่ถูกล้อมรอบ

ภูมิภาคที่ถูกล้อมรอบ (LeetCode #130) ค้นหาภูมิภาคที่ประกอบด้วย 'O' ทั้งหมดและถูกล้อมรอบด้วยขอบ 'X' อย่างสมบูรณ์ ภูมิภาคจะไม่ถูกจับไว้หากเซลล์ 'O' ใดเซลล์หนึ่งแตะขอบของกระดาน เคล็ดลับคือ แทนที่จะค้นหาภูมิภาคที่ถูกล้อมรอบโดยตรง ให้ทำ DFS จากเซลล์ 'O' ทั้งหมดที่อยู่ตามขอบ และทำเครื่องหมายทุกเซลล์ที่เข้าถึงได้ว่าไม่เป็นอันตราย จากนั้นจึงกลับค่า: เซลล์ 'O' ที่เหลือทั้งหมดถูกล้อมรอบและเปลี่ยนเป็น 'X' ส่วนเซลล์ที่ไม่เป็นอันตรายจะถูกคืนกลับเป็น 'O'

def solve(board):
    if not board:
        return
    rows, cols = len(board), len(board[0])

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if board[r][c] != 'O':
            return
        board[r][c] = 'S'  # safe: connected to border
        dfs(r+1,c); dfs(r-1,c)
        dfs(r,c+1); dfs(r,c-1)

    # Mark border-connected O's as safe
    for r in range(rows):
        dfs(r, 0); dfs(r, cols-1)
    for c in range(cols):
        dfs(0, c); dfs(rows-1, c)

    # Flip: surrounded O -> X, safe S -> O
    for r in range(rows):
        for c in range(cols):
            if board[r][c] == 'O': board[r][c] = 'X'
            elif board[r][c] == 'S': board[r][c] = 'O'

board = [['X','X','X','X'],['X','O','O','X'],
         ['X','X','O','X'],['X','O','X','X']]
solve(board)
print([board[1][1], board[3][1]])  # X, O

นับเกาะย่อย

นับเกาะย่อย (LeetCode #1905) ค้นหาเกาะใน grid2 ที่อยู่ภายในเกาะใน grid1 ทั้งหมด ทำ DFS จากเซลล์ '1' แต่ละเซลล์ใน grid2 โดยเกาะจะเป็นเกาะย่อยก็ต่อเมื่อทุกเซลล์ที่ DFS เข้าถึงเป็น '1' ใน grid1 ด้วย เคล็ดลับคือ ให้เข้าถึงเซลล์ ALL ของเกาะเพื่อทำเครื่องหมายว่าได้สำรวจแล้ว แต่ให้ติดตามว่าเซลล์ ALL เหล่านั้นเป็น '1' ใน grid1 ด้วยหรือไม่ อย่าหยุดทำงานทันทีเมื่อพบ '0' ตัวแรกใน grid1 เพราะคุณจะพลาดการทำเครื่องหมายเซลล์อื่นของเกาะเดียวกัน

def count_sub_islands(grid1, grid2):
    rows, cols = len(grid2), len(grid2[0])

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return True
        if grid2[r][c] != 1:
            return True
        grid2[r][c] = 0  # mark visited
        is_sub = grid1[r][c] == 1  # this cell must be in grid1
        is_sub = dfs(r+1,c) and is_sub  # note: AND not short-circuit OR
        is_sub = dfs(r-1,c) and is_sub
        is_sub = dfs(r,c+1) and is_sub
        is_sub = dfs(r,c-1) and is_sub
        return is_sub

    count = 0
    for r in range(rows):
        for c in range(cols):
            if grid2[r][c] == 1 and dfs(r, c):
                count += 1
    return count

print(count_sub_islands([[1,1,1],[1,0,1],[1,1,1]],
                         [[1,1,1],[1,0,1],[1,1,1]]))  # 1

DFS เทียบกับ BFS สำหรับองค์ประกอบที่เชื่อมต่อกัน

ทั้ง DFS และ BFS สามารถค้นหาองค์ประกอบที่เชื่อมต่อกันทั้งหมดได้อย่างถูกต้อง โดยมีความซับซ้อนด้านเวลา O(V + E) และความซับซ้อนด้านพื้นที่ O(V) เท่ากัน DFS นำไปเขียนแบบเรียกซ้ำได้ง่ายกว่าสำหรับปัญหาองค์ประกอบที่เชื่อมต่อกัน ขณะที่มักเลือกใช้ BFS เมื่อต้องการข้อมูลเส้นทางที่สั้นที่สุดด้วย ในปัญหาบนกริด DFS ใช้หน่วยความจำแคชได้มีประสิทธิภาพมากกว่า เพราะจะสำรวจลึกไปในทิศทางหนึ่งก่อนย้อนกลับ ทำให้เข้าถึงตำแหน่งหน่วยความจำใกล้เคียงกันตามลำดับ

# DFS advantages for connected components:
# - Simpler recursive implementation
# - Lower constant factor for small graphs
# - Can restore grid state during backtracking (if needed)

# BFS advantages:
# - Finds shortest path while traversing
# - Better for wide, shallow graphs (avoids deep recursion)
# - Multi-source initialisation is natural

# Same asymptotic complexity: O(V + E) time, O(V) space
# Grid (m rows, n cols): O(mn) time and space
print('DFS and BFS: same O(V+E) complexity for component counting')

เกาะที่มีข้อจำกัด: รูปร่างและเส้นรอบรูป

เส้นรอบรูปเกาะ (LeetCode #463) นับเส้นรอบรูปทั้งหมดของเกาะเดียวในกริด สำหรับเซลล์พื้นดินแต่ละเซลล์ ('1') ให้บวก 4 เข้าไปในเส้นรอบรูป จากนั้นลบ 2 สำหรับเซลล์พื้นดินที่อยู่ติดกันแต่ละเซลล์ (ขอบที่ใช้ร่วมกัน) วิธีที่ใช้สูตร O(mn) นี้ไม่ต้องใช้ DFS แต่การเข้าใจว่าวิธีนี้เทียบเท่ากับ DFS ที่นับขอบเขต จะช่วยย้ำความเชื่อมโยงระหว่างปัญหาบนกริดกับการให้เหตุผลเกี่ยวกับกราฟ

def island_perimeter(grid):
    rows, cols = len(grid), len(grid[0])
    perimeter = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 1:
                perimeter += 4  # start with 4 sides
                # Subtract shared edges with adjacent land cells
                if r > 0 and grid[r-1][c] == 1:
                    perimeter -= 2  # shared top edge
                if c > 0 and grid[r][c-1] == 1:
                    perimeter -= 2  # shared left edge
    return perimeter

grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
print(island_perimeter(grid))  # 16

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจแนวคิดเรื่องโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้เรื่อง องค์ประกอบที่เชื่อมต่อกัน ผ่าน DFS ที่ติดตามการเข้าชม เรื่อง จำนวนเกาะและการเติมพื้นที่ ในฐานะการประยุกต์ใช้กับกริด 2 มิติที่เป็นแบบมาตรฐาน รวมถึงรูปแบบขั้นสูง เช่น DFS แบบย้อนกลับจากขอบ (ภูมิภาคที่ถูกล้อมรอบ) และ การทำ DFS หลายครั้งพร้อมติดตามข้อจำกัด (เกาะย่อย) บทถัดไปเราจะจัดการกับการตรวจจับวัฏจักรในกราฟแบบมีทิศทางและไม่มีทิศทาง

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

บทเรียน “DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่”

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

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

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

บทเรียน “DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่” ใช้เวลานานแค่ไหน

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

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

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

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

  1. การแทนกราฟและการตั้งค่าการท่อง
  2. BFS: เส้นทางสั้นที่สุดและการท่องตามระดับ
  3. DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่
  4. การตรวจจับวัฏจักรในกราฟมีทิศทางและไม่มีทิศทาง
← กลับไปที่ Coding Interview Prep