coding test - python/SAMSUNG SWT(SW역량 테스트)

삼성 SW역량테스트 기출 / 2024 상반기 오후 1번 문제 마법의 숲 탐색 - 회전하면서 하강 / Python 파이썬

sillon 2024. 10. 2. 19:47
728x90
반응형

 

*문제 출처는 코드트리에 있습니다.

 

삼멘

문제 제목: 마법의 숲 탐색

문제 사이트: https://www.codetree.ai/training-field/frequent-problems/problems/magical-forest-exploration/description?page=1&pageSize=5

 

코드트리 | 코딩테스트 준비를 위한 알고리즘 정석

국가대표가 만든 코딩 공부의 가이드북 코딩 왕초보부터 꿈의 직장 코테 합격까지, 국가대표가 엄선한 커리큘럼으로 준비해보세요.

www.codetree.ai

 


코드 짜기 전에 먼저 구성한 sudo

'''
sudo
gravity: 쭉 내린다
더이상 못내리면 move = False
cr,cc,d 는 어떻게 관리? -> 전역변수?

for c,d in golems:

    cr,cc = 1,c
    idx = 1
    ncr, ncc, nd = cr, cc, d
    while True:
        grav = gravity()
        lr = left_rotate()
        rr = right_rotate()

        if grav== False and lr == False and rr == False:
        	break

    check = [[ncr+1,cc],[ncr,cc-1],[ncr,cc],[ncr,cc+1],[ncr-1,ncc]]

    idx += 1
'''

 

수정 후 코드

from collections import deque

R, C, K = map(int, input().rstrip().split())

maps = [[0] * (C + 1) for _ in range(R + 3)]
direct = ((-1, 0), (0, 1), (1, 0), (0, -1))
golem_point = {}  # 각 idx를 가진 골렘의 위치, 출구 방향 좌표
# c 골렘이 출발하는 열
for i in range(3, R + 3):
    maps[i][-1] = -1

# 골렘 정보
golem = [list(map(int, input().split())) for _ in range(K)]

row_score = 0  # 각 행에 속한 정령의 수


def left_rotate(idx):
    global golem_point
    # 왼쪽 확인
    cr, cc, d = golem_point[idx]
    check = [[cr - 1, cc - 1], [cr, cc - 2], [cr + 1, cc - 1], [cr + 1, cc - 2], [cr + 2, cc - 1]]
    for row, col in check:
        if 0 <= row < R + 3 and 0 <= col < C + 1 and maps[row][col] == 0:
            continue
        else:
            return False
    golem_point[idx][2] = (d - 1) % 4
    golem_point[idx][0], golem_point[idx][1] = cr + 1, cc - 1
    return True


def right_rotate(idx):
    global golem_point
    cr, cc, d = golem_point[idx]
    check = [[cr - 1, cc + 1], [cr, cc + 2], [cr + 1, cc + 1], [cr + 2, cc + 1], [cr + 1, cc + 2]]
    for row, col in check:
        if 0 <= row < R + 3 and 0 <= col < C + 1 and maps[row][col] == 0:
            continue
        else:
            return False
    golem_point[idx][2] = (d + 1) % 4
    golem_point[idx][0], golem_point[idx][1] = cr + 1, cc + 1
    return True


def exit_man(cr, cc, idx):
    queue = deque([[cr, cc, idx]])
    MAX_row = float('-inf')
    visited = [[False] * (C + 1) for _ in range(R + 3)]
    visited[cr][cc] = True
    while queue:
        q = queue.popleft()
        qx, qy, qidx = q[0], q[1], q[2]
        d = golem_point[qidx][2]  # 출구 정보
        dx, dy = golem_point[qidx][0] + direct[d][0], golem_point[qidx][1] + direct[d][1]
        for i in range(4):
            nx = qx + direct[i][0]
            ny = qy + direct[i][1]
            if 2 <= nx < R + 3 and 0 <= ny < C and maps[nx][ny] > 0 and visited[nx][ny] == False:
                if (qx == dx and qy == dy) or maps[nx][ny] == qidx:
                    # queue에 넣는다 => 다음에 갈 수 있다
                    queue.append([nx, ny, maps[nx][ny]])

                    visited[nx][ny] = True
                    if nx > MAX_row:
                        MAX_row = nx
                        if MAX_row == R + 2:
                            return MAX_row

    return MAX_row


def gravity(idx):
    global golem_point
    cr, cc, d = golem_point[idx]
    ncr = cr + 1
    while True:
        if ncr + 1 < len(maps) and maps[ncr][cc - 1] == 0 and maps[ncr][cc + 1] == 0 and maps[ncr + 1][cc] == 0:
            ncr += 1
        else:
            ncr -= 1
            break
    if ncr > cr:
        golem_point[idx] = [ncr, cc, d]
        return True
    else:
        return False


for g in range(len(golem)):
    # g = c, d #열과 출구 방향
    c, d = golem[g]
    # 골렘 정보를 바탕으로 좌표 세팅
    # 골렘 초기 위치 정보 저장
    c = c - 1
    cr, cc = 1, c
    idx = g + 1
    golem_point[idx] = [cr, cc, d]
    # golem_set(c, idx)
    while True:
        move1 = gravity(idx)
        move2 = left_rotate(idx)
        move3 = right_rotate(idx)
        if move1 == False and move2 == False and move3 == False:
            break
    ncx, ncy, d = golem_point[idx]
    check = [[ncx - 1, ncy], [ncx, ncy - 1], [ncx, ncy], [ncx, ncy + 1], [ncx + 1, ncy]]
    for nx, ny in check:
        maps[nx][ny] = idx

    new_maps = False
    for nx, ny in check:
        if nx < 3:
            maps = [[0] * (C + 1) for _ in range(R + 3)]
            # c 골렘이 출발하는 열
            for i in range(2, R + 3):
                maps[i][-1] = -1

            new_maps = True

    if new_maps == True:
        continue

    if nx > 3:
        MAX_row = exit_man(ncx, ncy, idx)
        if MAX_row > 0:
            row_score += MAX_row - 2

print(row_score)

 

수정 전에는 exit_man (bfs) 부분이 조건문이 이상했음.

 

큐에 넣고 방문처리를 해줘야하는데,

방문만 하면 무조건 True를 갈겨버려서

틀렷음..

수정된 부분

 

def exit_man(cr, cc, idx):
    queue = deque([[cr, cc, idx]])
    MAX_row = float('-inf')
    visited = [[False] * (C + 1) for _ in range(R + 3)]
    visited[cr][cc] = True
    while queue:
        q = queue.popleft()
        qx, qy, qidx = q[0], q[1], q[2]
        d = golem_point[qidx][2]  # 출구 정보
        dx, dy = golem_point[qidx][0] + direct[d][0], golem_point[qidx][1] + direct[d][1]
        for i in range(4):
            nx = qx + direct[i][0]
            ny = qy + direct[i][1]
            if 2 <= nx < R + 3 and 0 <= ny < C and maps[nx][ny] > 0 and visited[nx][ny] == False:
                if (qx == dx and qy == dy) or maps[nx][ny] == qidx:
                    # queue에 넣는다 => 다음에 갈 수 있다
                    queue.append([nx, ny, maps[nx][ny]])

                    visited[nx][ny] = True
                    if nx > MAX_row:
                        MAX_row = nx
                        if MAX_row == R + 2:
                            return MAX_row

    return MAX_row

 

수정 전 코드

def exit_man(cr, cc, idx):
    queue = deque([[cr, cc, idx]])
    MAX_row = float('-inf')
    visited = [[False] * (C + 1) for _ in range(R + 3)]
    while queue:
        q = queue.popleft()
        qx, qy, qidx = q[0], q[1], q[2]
        d = golem_point[qidx][2]  # 출구 정보
        dx, dy = golem_point[qidx][0] + direct[d][0], golem_point[qidx][1] + direct[d][1]
        for i in range(4):
            nx = qx + direct[i][0]
            ny = qy + direct[i][1]
            if 2 <= nx < R + 3 and 0 <= ny < C and maps[nx][ny] > 0 and visited[nx][ny] == False:
                visited[nx][ny] = True # 문제의 부분 두둥탁
                if nx > MAX_row:
                    MAX_row = nx
                    if MAX_row == R + 2:
                        return MAX_row
                if (nx == dx and ny == dy) or (qx == dx and qy == dy) or maps[nx][ny] == qidx:
                    queue.append([nx, ny, maps[nx][ny]])
    return MAX_row

2024.10.12

 

복기 코드

- X,Y 좌표 방향 주의 하기

- 조건절 할때 항상 생각 한번 더 하고!! 짜기

더보기
from collections import deque

R, C, K = map(int, input().rstrip().split())

maps = [[0] * (C + 1) for _ in range(R + 3)]
direct = ((-1, 0), (0, 1), (1, 0), (0, -1)) # 북동남서
# c 골렘이 출발하는 열
for i in range(3, R + 3):
    maps[i][-1] = -1
# 골렘 정보
golem = [list(map(int, input().split())) for _ in range(K)]
# 골렘의 초기 c열과, 방향이 주어짐
golem_point = {i+1:[1,golem[i][0]-1,golem[i][1]] for i in range(len(golem))} # 처음에는 1행에서 시작
exit_map = [[False]*(C+1) for _ in range(R+3)]
answer = 0
def bfs(idx):
    # 탈출구 방향 유의
    cx,cy,_ = golem_point[idx]
    queue = deque([[cx,cy]])
    visited = [[False] * (C+1) for _ in range(R+3)]
    visited[cx][cy] = True
    MAX = 0
    while queue:
        q = queue.popleft()
        qx,qy = q[0], q[1]
        if qx == len(maps):
            return qx - 2
        if MAX < qx:
            MAX = qx
        c_idx = maps[qx][qy] # 현재 위치의 인덱스
        d = golem_point[c_idx][2]
        # ex, ey = golem_point[c_idx][0] + direct[d][0], golem_point[c_idx][1] + direct[d][1]
        for i in range(4):
            x = qx + direct[i][0]
            y = qy + direct[i][1]
            if 0 <= x < R+3 and 0 <= y < C and visited[x][y] == False and maps[x][y] > 0:
                # 이동한 위치가 해당 골렘의 영역이거나
                # 현재 위치가 해당 골렘의 출구라면
                if maps[x][y] == c_idx or exit_map[qx][qy] == True:
                    queue.append([x,y])
                    visited[x][y] = True

    return MAX - 2

def gravity(idx):
    # 가장 남쪽으로 내려가본다.
    cx,cy,d = golem_point[idx]
    ncx = cx
    while True:
        if ncx +1< len(maps) and maps[ncx+1][cy] == 0 and maps[ncx][cy+1] == 0 and maps[ncx][cy-1] == 0:
            ncx += 1
        else:
            ncx -= 1
            break
    if ncx > cx:
        golem_point[idx] = [ncx,cy,d]
        return True
    else:
        return False

# rotate 시 출구 방향도 함꼐 rotate 됨

def left_rotate(idx):
    global golem_point
    cx,cy,d= golem_point[idx]
    gm = [[cx,cy-2],[cx-1,cy-1],[cx+1,cy-1],[cx+1,cy-2],[cx+2,cy-1]]
    for x,y in gm:
        if 0<= x < R+3 and 0 <= y < C and maps[x][y] == 0:
            continue
        else:
            return False
    nd = (d-1) % 4
    golem_point[idx] = [cx+1,cy-1,nd]
    return True

def right_rotate(idx):
    global golem_point
    cx,cy,d= golem_point[idx]
    gm = [[cx,cy+2],[cx-1,cy+1],[cx+1,cy+1],[cx+1,cy+2],[cx+2,cy+1]]
    for x,y in gm:
        if 0<= x < R+3 and 0 <= y < C and maps[x][y] == 0:
            continue
        else:
            return False
    nd = (d+1) % 4
    golem_point[idx] = [cx+1,cy+1,nd]
    return True

for idx in golem_point:
    while True:
        # [1] 남쪽으로 내려간다
        move1 = gravity(idx)
        # [2] 남쪽으로 못내려가면 서쪽 방향으로 회전한다.
        move2 = left_rotate(idx)
        # # [3] 서쪽으로도 못내려가면 동쪽으로 회전한다.
        move3 = right_rotate(idx)
        # [4] 더이상 내려갈 방향이 없으면 탈출한다.
        if move1 == False and move2 == False and move3 == False:
            break

    # 골렘 최종 좌표 표시
    cx,cy,d = golem_point[idx]

        # [4-0] 만약 골렘의 몸 일부가 숲을 벗어나지 않았다면 좌표 표시
    new_ = False
    if 4 > cx :
        new_ = True
    if new_ == False:
        for x, y in [[cx, cy], [cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]]:
            maps[x][y] = idx
        ex,ey = cx + direct[d][0],cy + direct[d][1]
        exit_map[ex][ey] = True
        # [4-0-1] 탈출했을때 탈출구가 이어지면 옆 골렘으로 이동 가능
        score = bfs(idx)
        # [4-0-2] 가장 밑으로 내려왔을때 행의 높이를 적는다.
        answer += score
    # [4-1] 골렘의 몸 일부가 숲을 벗어났다면, 해당 골렘은 죽이고(점수X) 새로운 맵
    else:
        maps = [[0] * (C + 1) for _ in range(R + 3)]
        exit_map = [[False] * (C + 1) for _ in range(R + 3)]

    # for i in maps:
    #     print(i)
    # print()
print(answer)

 

수정 전 코드와 수정 후 코드의 차이 분석

1. 수정 전 코드:

  • visited[nx][ny] = True 부분이 queue.append와 상관없이 바로 실행된다. 즉, 이동 가능한지 확인하기 전에 해당 좌표를 방문 처리하고 있다.
  • 조건절에서 (nx == dx and ny == dy), (qx == dx and qy == dy), maps[nx][ny] == qidx 세 가지 경우 중 하나가 성립하면 큐에 새로운 좌표를 추가한다.

2. 수정 후 코드:

  • visited[cr][cc] = True로 시작 좌표를 먼저 방문 처리하여 중복 방문을 방지한다.
  • 큐에 좌표를 추가하는 부분을 조건문 (qx == dx and qy == dy) 또는 maps[nx][ny] == qidx에 의해서만 실행되도록 변경했다.
  • 큐에 좌표를 추가한 후에 visited[nx][ny] = True를 설정하여, 실제로 큐에 추가된 좌표만 방문 처리한다. 이를 통해 불필요한 방문 처리를 방지하고, 잘못된 경로 탐색을 막는다.

조건 해결 방법

수정 후에는 이동 가능한지 확인한 후에만 큐에 추가하고 방문 처리를 하므로, 중복 방문과 불필요한 경로 탐색을 막아준다. visited를 큐에 넣기 전에 설정하지 않고, 큐에 추가된 좌표만 방문 처리하는 방식으로 바꿔야 조건에 맞는 좌표 탐색을 효율적으로 수행할 수 있다.

 
더보기


수정 전 전체 코드

from collections import deque

R, C, K = map(int, input().rstrip().split())

maps = [[0] * (C + 1) for _ in range(R + 3)]
direct = ((-1, 0), (0, 1), (1, 0), (0, -1))
golem_point = {}  # 각 idx를 가진 골렘의 위치, 출구 방향 좌표
# c 골렘이 출발하는 열
for i in range(3, R + 3):
    maps[i][-1] = -1

# 골렘 정보
golem = [list(map(int, input().split())) for _ in range(K)]

row_score = 0  # 각 행에 속한 정령의 수


def left_rotate(idx):
    global golem_point
    # 왼쪽 확인
    cr, cc, d = golem_point[idx]
    check = [[cr - 1, cc - 1], [cr, cc - 2], [cr + 1, cc - 1], [cr + 1, cc - 2], [cr + 2, cc - 1]]
    for row, col in check:
        if 0 <= row < R + 3 and 0 <= col < C + 1 and maps[row][col] == 0:
            continue
        else:
            return False
    golem_point[idx][2] = (d - 1) % 4
    golem_point[idx][0], golem_point[idx][1] = cr + 1, cc - 1
    return True


def right_rotate(idx):
    global golem_point
    cr, cc, d = golem_point[idx]
    check = [[cr - 1, cc + 1], [cr, cc + 2], [cr + 1, cc + 1], [cr + 2, cc + 1], [cr + 1, cc + 2]]
    for row, col in check:
        if 0 <= row < R + 3 and 0 <= col < C + 1 and maps[row][col] == 0:
            continue
        else:
            return False
    golem_point[idx][2] = (d + 1) % 4
    golem_point[idx][0], golem_point[idx][1] = cr + 1, cc + 1
    return True


def exit_man(cr, cc, idx):
    queue = deque([[cr, cc, idx]])
    MAX_row = float('-inf')
    visited = [[False] * (C + 1) for _ in range(R + 3)]
    while queue:
        q = queue.popleft()
        qx, qy, qidx = q[0], q[1], q[2]
        d = golem_point[qidx][2]  # 출구 정보
        dx, dy = golem_point[qidx][0] + direct[d][0], golem_point[qidx][1] + direct[d][1]
        for i in range(4):
            nx = qx + direct[i][0]
            ny = qy + direct[i][1]
            if 2 <= nx < R + 3 and 0 <= ny < C and maps[nx][ny] > 0 and visited[nx][ny] == False:
                visited[nx][ny] = True
                if nx > MAX_row:
                    MAX_row = nx
                    if MAX_row == R + 2:
                        return MAX_row
                if (nx == dx and ny == dy) or (qx == dx and qy == dy) or maps[nx][ny] == qidx:
                    queue.append([nx, ny, maps[nx][ny]])
    return MAX_row


def gravity(idx):
    global golem_point
    cr, cc, d = golem_point[idx]
    ncr = cr + 1
    while True:
        if ncr + 1 < len(maps) and maps[ncr][cc - 1] == 0 and maps[ncr][cc + 1] == 0 and maps[ncr + 1][cc] == 0:
            ncr += 1
        else:
            ncr -= 1
            break
    if ncr > cr:
        golem_point[idx] = [ncr, cc, d]
        return True
    else:
        return False


def golem_set(c, idx):
    global maps
    golem_body = ((0, c), (1, c - 1), (1, c), (1, c + 1), (2, c))
    new_maps = False
    for x, y in golem_body:
        if maps[x][y] != 0:
            new_maps = True
            break
    if new_maps:
        maps = [[0] * (C + 1) for _ in range(R + 3)]
        # c 골렘이 출발하는 열
        for i in range(2, R + 3):
            maps[i][-1] = -1


for g in range(len(golem)):
    # g = c, d #열과 출구 방향
    c, d = golem[g]
    # 골렘 정보를 바탕으로 좌표 세팅
    # 골렘 초기 위치 정보 저장
    c = c - 1
    cr, cc = 1, c
    idx = g + 1
    golem_point[idx] = [cr, cc, d]
    golem_set(c, idx)
    while True:
        move1 = gravity(idx)
        move2 = left_rotate(idx)
        move3 = right_rotate(idx)
        if move1 == False and move2 == False and move3 == False:
            break
    ncx, ncy, d = golem_point[idx]
    check = [[ncx - 1, ncy], [ncx, ncy - 1], [ncx, ncy], [ncx, ncy + 1], [ncx + 1, ncy]]
    for nx, ny in check:
        maps[nx][ny] = idx
    if nx > 3:
        MAX_row = exit_man(ncx, ncy, idx)

        if MAX_row > 0:
            row_score += MAX_row - 2

    # for i in maps:
    #     print(i)
    # print()
print(row_score)

 

1번 테케 통과

2번 테케 X

7 9 6
4 1
5 1
2 1
8 1
2 2
6 0

 

※ 알아야 할 것

- 수정 전 코드는 방문 여부를 큐에 추가하기 전에 설정하여 불필요한 경로 탐색이 발생했다.

- 수정 후에는 큐에 좌표를 넣은 후에만 방문 처리를 하여 중복 방문을 방지했다.

- 조건에 맞는 좌표만 큐에 추가하고 방문 처리해야 경로 탐색이 정확해진다.

728x90
반응형