본문 바로가기

코딩테스트

[Python] DFS 문제 풀이 정리

DFS 정복하기.

물론 이분탐색이나 BFS도 있지만 내가 느끼기에 상대적으로 많이 보이는 건 DFS이다. 그래서 DFS를 중심으로 문제 연습!

 


https://www.acmicpc.net/problem/2468

 

2468번: 안전 영역

재난방재청에서는 많은 비가 내리는 장마철에 대비해서 다음과 같은 일을 계획하고 있다. 먼저 어떤 지역의 높이 정보를 파악한다. 그 다음에 그 지역에 많은 비가 내렸을 때 물에 잠기지 않는

www.acmicpc.net

import sys
sys.setrecursionlimit(100000)
input=sys.stdin.readline

n = int(input()) # 2차원 행/열
# visited= [[False]*(n) for _ in range(n)]
maxNum = 0 # 최대 높이 저장
arr = [] # 높이 배열
# print(visited)
for i in range(n):
    arr.append(list(map(int, input().split())))
    for j in range(n):
        if arr[i][j] > maxNum:
            maxNum = arr[i][j] 

dx = [0 ,0, 1, -1]
dy = [1, -1, 0 ,0]

def DFS(x, y, h):
    for i in range(4):
        nx = x + dx[i]
        ny = y + dy[i]
        if (0<=nx<n) and (0<=ny<n) and not visited[nx][ny] and arr[nx][ny]>h:
        # 다음에 이동할 장소가 유효하고, 방문하지 않았으며, h보다 높이가 높을 때!
            visited[nx][ny]=True
            DFS(nx,ny,h)



ans = 1
for h in range(maxNum): # 최대 높이 계속 늘리기
    visited= [[False]*(n) for _ in range(n)] # 강수량이 바뀔때마다 안전영역을 다시 계산해야 하므로 안에다가 선언!
    count = 0 # DFS 횟수인 count도 마찬가지. 안전 영역의 수가 된다.
    for i in range(n):
        for j in range(n):
            if arr[i][j] > h and not visited[i][j]:
                count += 1
                visited[i][j] = True
                DFS(i, j, h)
    ans = max(ans, count) # 최댓값 저장

print(ans)
  • 시뮬레이션 + DFS 문제!
  • 상하좌우를 바꿔가며 만약 방문하지 않았고, 상하좌우 이동이 가능하며 최대 눌 높이가 높은 경우 DFS를 실행(탐색
  • 따라서 물 높이는 0부터(비가 안오는 경우) 최대 높이 -1까지(최대 높이면 안전 영역은 0이다. 최댓값을 찾아므로 탐색 X)까지 물 높이 h를 바꿔가며 안전영역의 수를 탐색한다.
  • 안전영역의 수(count)는 DFS가 실행된 횟수이다.
  • count의 최댓값을 찾는다. 따라서 물 높이를 바꿀때마다(for문이 실행될때마다) count와 visited배열(스택)은 초기화 시켜줘야 함.

https://www.acmicpc.net/problem/4963

 

4963번: 섬의 개수

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 지도의 너비 w와 높이 h가 주어진다. w와 h는 50보다 작거나 같은 양의 정수이다. 둘째 줄부터 h개 줄에는 지도

www.acmicpc.net

import sys
sys.setrecursionlimit(100000)
input=sys.stdin.readline

while True:
    w,h = map(int, input().split()) # 너비, 높이(열, 행)==(가로, 세로)
    if w==h==0:
        break
    arr=[]
    visited=[[False]*w for _ in range(h)]
    for i in range(h):
        arr.append(list(map(int, input().split()))) # 지도. 1은 땅, 0은 바다.

    dx = [0 ,0, 1, -1, 1, 1, -1, -1] # 상하좌우대각선
    dy = [1, -1, 0 ,0, 1, -1, 1, -1]

    def DFS(x, y): # 가로, 세로로 입력 받음
        for i in range(8):
            nx = x + dx[i]
            ny = y + dy[i]
            # print(nx, ny)
            if (0<=nx<w) and (0<=ny<h) and not visited[ny][nx] and arr[ny][nx]==1:
            # 다음에 이동할 장소가 유효하고, 방문하지 않았고, 다음 장소가 땅이면!
                # print(ny, nx)
                visited[ny][nx]=True
                # print(visited)
                DFS(nx, ny) 
            

    count = 0
    for i in range(h):
        for j in range(w):
            if not visited[i][j] and arr[i][j]==1: # 방문하지 않았고 땅이면
                visited[i][j] = True
                count += 1
                DFS(j,i)

    print(count)
  • 대각선까지 추가된 DFS 문제. 어렵지는 않다.
  • 내가 틀렸던 점은 가로, 세로를 생각 없이 넣은 것때문이다...
    • 가로==너비(w)==열==j==x
    • 세로==높이(h)==행==i==y
  • DFS 입력은 가로 위치, 세로위치로 입력 받는다. 대신 다른 배열들을 접근할 때는 [행][열] 인덱스로 접근하므로 반대로 작성해줘야 함을 잊지 말자..!

https://www.acmicpc.net/problem/2583

 

2583번: 영역 구하기

첫째 줄에 M과 N, 그리고 K가 빈칸을 사이에 두고 차례로 주어진다. M, N, K는 모두 100 이하의 자연수이다. 둘째 줄부터 K개의 줄에는 한 줄에 하나씩 직사각형의 왼쪽 아래 꼭짓점의 x, y좌표값과 오

www.acmicpc.net

for _ in range(k): # 영역 분리!
    x1, y1, x2, y2 = map(int, input().split())
    for i in range(y2, y1, -1):
        for j in range(x1, x2):
            visited[m-i][j]=True
  • 첫번째로 어려웠던 분리할 구역 입력 받기!
  • 왼쪽 아래를 0,0으로 가정하므로 잘 생각해서 인덱스를 잘 고려해서 입력받는다.
# 틀린 답

answer = [] # 넓이 저장 리스트
dx = [0 ,0, 1, -1,] # 상하좌우
dy = [1, -1, 0 ,0]

def DFS(x, y, area):
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if 0<=nx<n and 0<=ny<m and not visited[ny][nx]:
            visited[ny][nx]=True
            area += 1
            DFS(nx, ny, area)
    if len(answer)<count: # 넓이 저장. 단, 넓이는 계속 작아지므로 처음 return하는 값만 저장
        answer.append(area)
    

count = 0

for i in range(m):
    for j in range(n):
        area = 0
        if not visited[i][j]:
            count += 1
            area += 1
            visited[i][j] = True
            DFS(j, i, area)
  • area를 어떻게 전달하는가...젤 어려웠다.
  • DFS안에서 DFS를 호출하는 경우를 잘 고려해서 area값을 전달해야 한다. 
# 정답

def DFS(x, y, area):
    for i in range(4):
        nx = x+dx[i]
        ny = y+dy[i]
        if 0<=nx<n and 0<=ny<m and not visited[ny][nx]:
            visited[ny][nx]=True
            area = DFS(nx, ny, area+1)
    return area
    

count = 0

for i in range(m):
    for j in range(n):
        area = 0
        if not visited[i][j]:
            count += 1
            area += 1
            visited[i][j] = True
            answer.append(DFS(j, i, area))   
            

print(count)
print(*(sorted(answer)))
  • DFS에서 DFS를 다시 호출하려면 저렇게 함수 안에서 직접 수를 더한 후 전달해야 한다.
  • 아니면 전역 변수로 선언해서..area값을 함수에서 공통적으로 다룰 수 있도록 한다.

https://www.acmicpc.net/problem/2644

 

2644번: 촌수계산

사람들은 1, 2, 3, …, n (1 ≤ n ≤ 100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어

www.acmicpc.net

import sys
sys.setrecursionlimit(100000)
input=sys.stdin.readline

n = int(input())
arr = [[] for _ in range(n+1)]
num1, num2 = map(int, input().split()) # 구해야 하는 촌수
visited = [False]*(n+1)
result = []

for _ in range(int(input())):
    a, b = map(int, input().split())
    arr[a].append(b)
    arr[b].append(a)

def DFS(v, k): # k는 촌수
    k+=1 # 촌수 +1
    visited[v] = True
    if v == num2:
        result.append(k)
    for i in arr[v]:
        if not visited[i]:
            DFS(i, k)



DFS(num1, 0)
if len(result)==0:
    print(-1)
else:
    print(result[0]-1)
  • 처음에는 result 함수에 방문하는 정점을 순서대로 저장하고 그 간격을 출력하려 했다.
  • 그런데 그러면 동일한 촌수(2촌)를 인식하지 못하므로 폐기...깊이가 깊어질 때 1을 추가하는 코드로 구현해야 한다.
  • DFS를 호출할때마다 촌수는 +1이 된다.
  • 또한 1부터 시작하는 것이 아닌, num1부터 시작하므로 거꾸로 올라갈때도 계산이 가능해진다.

https://www.acmicpc.net/problem/1926

 

1926번: 그림

어떤 큰 도화지에 그림이 그려져 있을 때, 그 그림의 개수와, 그 그림 중 넓이가 가장 넓은 것의 넓이를 출력하여라. 단, 그림이라는 것은 1로 연결된 것을 한 그림이라고 정의하자. 가로나 세로

www.acmicpc.net

import sys
sys.setrecursionlimit(10000000) # 100000 는 recursion error
input=sys.stdin.readline

n, m = map(int, input().split()) # 세로, 가로
arr= []
visited = [[False]*m for _ in range(n)]

for _ in range(n):
    arr.append(list(map(int, input().split())))

dx = [0,0,-1,1]
dy = [-1,1,0,0]
num1 = 0 # 개수
num2 = 0 # 넓이
def DFS(x, y, k): # 가로,세로, 넓이
    visited[y][x]=True
    for i in range(4):
        nx = x +dx[i]
        ny = y +dy[i]
        if 0<=nx<m and 0<=ny<n and not visited[ny][nx] and arr[ny][nx]==1:
            k = DFS(nx, ny, k+1)
    return k

for i in range(n):
    for j in range(m):        
        if not visited[i][j] and arr[i][j]==1:
            num1 += 1
            num2 = max(num2, DFS(j, i, 1))

print(num1)
print(num2)
  • 그림의 갯수(num1)는 새로운 DFS를 돌릴 때마다 +1 해주기.
  • 그림의 최대 넓이(num2)는 DFS를 돌릴 때 넓이(k)를 +1씩 하며 반복하고, 끝나면 num2와 비교하여 최댓값을 저장한다.
  • 100000는 recursive error가 난다. 추가해주었음.

https://www.acmicpc.net/problem/1325

 

1325번: 효율적인 해킹

첫째 줄에, N과 M이 들어온다. N은 10,000보다 작거나 같은 자연수, M은 100,000보다 작거나 같은 자연수이다. 둘째 줄부터 M개의 줄에 신뢰하는 관계가 A B와 같은 형식으로 들어오며, "A가 B를 신뢰한

www.acmicpc.net

# 내 풀이(DFS)
import sys
sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

n, m = map(int, input().split())
arr= [[] for _ in range(n+1)]
visited = [0]*(n+1) # False보다 int형이 빠르다함ㄴ

for _ in range(m):
    a, b = map(int, input().split())
    # arr[a].append(b)
    arr[b].append(a)

def DFS(v, k): 
    for i in arr[v]:
        if not visited[i]:
            visited[i]=1
            k = DFS(i, k+1)

    return k

answer = []

for i in range(1, n+1):
    visited = [0]*(n+1)
    if not visited[i]:
        visited[i]=1
        answer.append((i, DFS(i, 1)))
    
answer.sort(key = lambda x: (-x[1], x[0])) # 두번째 원소(해킹 가능한 수)는 내림차순, 컴퓨터 번호는 오름차순
# 난 못풀었다

from collections import deque
import sys
input = sys.stdin.readline

# 입력
N, M = map(int, input().split())
graph = [[] for _ in range(N+1)]

for _ in range(M):
    A, B = map(int, input().split())
    graph[B].append(A)

# bfs
ans = []
for i in range(1, N+1):
    visited = [False]*(N+1)
    queue = deque([i])
    visited[i] = True
    
    cnt = 1
    while queue:
        x = queue.popleft()
        for j in graph[x]:
            if not visited[j]:
                visited[j] = True
                queue.append(j)
                cnt += 1
    ans.append(cnt)

max_cnt = max(ans)
for i in range(N):
    if ans[i] == max_cnt:
        print(i+1, end=" ")
  • DFS로 풀면 시간/메모리 초과가 나는 문제. 왜죠?
  • 또 위와 같이 BFS로 풀더라고 Python은 너무 느려서 안된다. PyPy로 제출해야 함.
  • BFS로 푸는 법도 까먹지 말고...비록 틀렸지만 이 문제는 단방향 연결리스트 여서 정리하고 싶었다. 계속 안나오더라.

https://www.acmicpc.net/problem/1743

 

1743번: 음식물 피하기

첫째 줄에 통로의 세로 길이 N(1 ≤ N ≤ 100)과 가로 길이 M(1 ≤ M ≤ 100) 그리고 음식물 쓰레기의 개수 K(1 ≤ K ≤ N×M)이 주어진다.  그리고 다음 K개의 줄에 음식물이 떨어진 좌표 (r, c)가 주어진다

www.acmicpc.net

import sys
sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

n, m, k = map(int, input().split()) # 세로, 가로, 음식물 개수
arr = [[0]*m for _ in range(n)]
visited = [[False]*m for _ in range(n)]

for _ in range(k):
    a, b = map(int, input().split())
    arr[a-1][b-1] = 1

dx = [0,0,-1,1]
dy = [-1,1,0,0]

def DFS(x, y, num):
    visited[y][x] = True
    for i in range(4):
        nx = x +dx[i]
        ny = y +dy[i]
        if 0<=nx<m and 0<=ny<n and arr[ny][nx]==1 and not visited[ny][nx]:
            num = DFS(nx, ny, num+1)
    return num

max_num = 0
for i in range(n):
    for j in range(m):
        if not visited[i][j] and arr[i][j]==1:
            max_num=max(max_num, DFS(j,i,1))

print(max_num)
  • 좌표가 (1,1) 부터 시작하는 것을 잊지 말자. 인덱스로 변환하기 위해 -1씩 해서 좌표를 저장했다.

https://www.acmicpc.net/problem/2210

 

2210번: 숫자판 점프

111111, 111112, 111121, 111211, 111212, 112111, 112121, 121111, 121112, 121211, 121212, 211111, 211121, 212111, 212121 이 가능한 경우들이다.

www.acmicpc.net

import sys
sys.setrecursionlimit(10000000) 
input=sys.stdin.readline

arr = []
# visited = [[0]*5 for _ in range(5)]
answer = []

for _ in range(5):
    arr.append(list(map(str, input().split())))

dx = [0,0,-1,1]
dy = [-1,1,0,0]

def DFS(x, y, num):
    # visited[y][x] = True
    if len(num)==6:
        if num not in answer:
            answer.append(num)
        return
    for i in range(4):
        nx = x +dx[i]
        ny = y +dy[i]
        if 0<=nx<5 and 0<=ny<5:
            DFS(nx, ny, num+arr[ny][nx])

for i in range(5):
    for j in range(5):    
        DFS(j,i,arr[i][j])

print(len(answer))
  • 되돌아갈 수 있다. 즉 visited를 판별하지 않아도 돼서 더욱 간단했던 문제. 대신 DFS를 돌면서 문자를 더해주며 문자열을 완성시켜 나가야 한다.
  • 문자열의 길이가 6이되면 멈추고 판별 후 답 리스트에 넣기. 출력은 리스트의 길이.
  • 브루트 포스 알고리즘인 만큼 입력도 작아서 생각할게 없다 ㅎㅎ

# 회고

 

DFS를 하루종일 풀었다.

이번에는 실버 위주로 풀었는데...조금만 더 연습하고 골드도 해봐야지...

DFS를 잘 응용해서 DFS가 실행된 횟수/DFS 내부 변수 추가 등등 DFS 자체는 어렵지 않은데 응용 생각하는데 꽤 걸린 문제들이 있었다.

개인적으로 풀면서 양방향/단방향을 생각해보고 싶었는데 한문제뿐이라 아쉽다. 실버라 그런가?