CODINGTEST

[코딩테스트] 현대오토 2026-04-05 회고

ch010104 2026. 4. 5. 17:55

1. 탐색 문제

""" N x M 크기의 격자판에 1부터 N x M까지의 숫자가 순서대로 적혀 있습니다. 
    당신은 1번 칸에서 출발하여 주어진 targets 리스트에 포함된 모든 숫자를 한 번씩 방문해야 합니다. 
    숫자를 방문하는 순서는 상관없으며, 이미 지나간 칸을 다시 지나가거나 이미 방문한 숫자를 다시 밟는 것도 허용됩니다. 
    격자판의 상하좌우로 한 칸 이동하는 데 거리가 1일 때, 모든 targets를 방문하고 멈추는 최단 거리를 구하세요.
    입력 예시: m=5, n=2, targets=[1, 5, 8]출력 예시: 6"""

from collections import deque

def solve(m, n, targets):
    # ---------------------------------------------------------
    # 1. 좌표 변환 (Preprocessing)
    # 1차원 숫자(노드 번호)를 2차원 격자상의 (r, c) 좌표로 변환합니다.
    # ---------------------------------------------------------
    target_pos = {}
    for num in targets:
        # 숫자가 1부터 시작하므로 인덱스 계산을 위해 1을 빼줍니다.
        # 예: m=5일 때 숫자 5는 인덱스 4 -> (4//5, 4%5) = (0, 4)
        r = (num - 1) // m
        c = (num - 1) % m
        target_pos[num] = (r, c)
    
    # ---------------------------------------------------------
    # 2. 두 지점 간 최단 거리 측정 함수 (BFS)
    # 지도를 직접 탐색하여 start_node에서 end_node까지의 최소 칸 수를 구합니다.
    # ---------------------------------------------------------
    def get_dist(start_node, end_node):
        sr, sc = target_pos[start_node] # 출발지 (행, 열)
        er, ec = target_pos[end_node]   # 목적지 (행, 열)
        
        # 큐 데이터 구성: (현재_행, 현재_열, 지금까지_이동_거리)
        queue = deque([(sr, sc, 0)])
        
        # 방문 체크: 특정 칸을 중복 방문하지 않기 위함 (해당 BFS 차수 내에서만 유효)
        visited = [[False] * m for _ in range(n)]
        visited[sr][sc] = True
        
        # 방향 벡터: 상, 하, 좌, 우
        dr = [-1, 1, 0, 0]
        dc = [0, 0, -1, 1]
        
        while queue:
            curr_r, curr_c, dist = queue.popleft()
            
            # 목적지 좌표에 도달한 경우 현재까지의 거리를 즉시 반환
            if curr_r == er and curr_c == ec:
                return dist
            
            # 4방향 탐색
            for i in range(4):
                nr, nc = curr_r + dr[i], curr_c + dc[i]
                
                # 격자 범위 내에 있고, 아직 방문하지 않은 칸이라면 큐에 추가
                if 0 <= nr < n and 0 <= nc < m and not visited[nr][nc]:
                    visited[nr][nc] = True
                    queue.append((nr, nc, dist + 1))
        
        return float('inf') # 도달 불가능할 경우 (문제 조건상 발생 안 함)

    # ---------------------------------------------------------
    # 3. 거리 행렬 생성 (Distance Matrix)
    # 모든 타겟 노드들끼리의 거리를 미리 계산해둡니다. (이후 지도 탐색은 더 이상 필요 없음)
    # ---------------------------------------------------------
    dist_matrix = {u: {v: 0 for v in targets} for u in targets}
    for u in targets:
        for v in targets:
            if u != v:
                # u에서 v로 가는 최단 거리를 BFS로 측정하여 저장
                dist_matrix[u][v] = get_dist(u, v)

    # ---------------------------------------------------------
    # 4. 최적 경로 탐색 (DFS / Backtracking)
    # 어떤 순서로 방문해야 전체 합계가 최소가 될지 모든 경우의 수를 따집니다.
    # ---------------------------------------------------------
    self_min_dist = float('inf') # 정답을 저장할 변수 (최솟값을 찾기 위해 무한대로 초기화)
    
    # 방문 여부 체크: 'targets' 내의 노드 방문 여부
    is_visited = {node: False for node in targets}
    is_visited[1] = True # 시작점인 1번 노드는 이미 방문한 상태로 시작
    
    def find_path(curr, count, total_d):
        """
        curr: 현재 위치한 노드 번호
        count: 지금까지 방문 완료한 노드의 개수
        total_d: 지금까지 누적된 이동 거리
        """
        nonlocal self_min_dist
        
        # [가지치기] 현재 거리가 이미 찾은 최솟값보다 크다면 더 이상 탐색할 가치가 없음
        if total_d >= self_min_dist: 
            return
            
        # [기저 사례] 모든 타겟 노드를 다 방문했다면 최솟값 갱신 후 종료
        if count == len(targets):
            self_min_dist = min(self_min_dist, total_d)
            return
            
        # 다음에 방문할 노드를 하나씩 선택
        for next_node in targets:
            if not is_visited[next_node]:
                is_visited[next_node] = True # 방문 표시
                # 다음 노드로 이동 (누적 거리 합산)
                find_path(next_node, count + 1, total_d + dist_matrix[curr][next_node])
                is_visited[next_node] = False # 백트래킹: 돌아와서 방문 표시 해제

    # 재귀 시작: 1번 노드에서, 방문 수 1개, 총 거리 0
    find_path(1, 1, 0)
    
    return self_min_dist

# 테스트 케이스 실행
print(solve(5, 2, [1, 5, 8])) # 예상 결과: 6

 


2. 적 무찌르기

"""당신은 적들을 차례로 무찌르는 게임을 하고 있습니다. 각 적은 정수 값의 체력을 가지고 있으며, 이 값은 양수(체력 회복)일 수도 있고 음수(체력 감소)일 수도 있습니다.
    1. 당신의 초기 체력은 자유롭게 설정할 수 있지만, 게임 도중이나 적을 무찌른 직후에 체력이 0 이하가 되면 패배합니다. (최소 1 유지)
    2. 적을 무찌르면 (나의 체력 + 적의 체력)이 나의 새로운 체력이 됩니다.
    3. 주어진 적들 중 k명을 선택하여 무찌를 때, 필요한 초기 체력의 최솟값을 구해야 합니다. k명의 적을 선택하는 순서와 조합은 자유롭습니다.
    4. 1명부터 전체 인원 N명까지 무찌를 때 각각 필요한 최소 초기 체력을 리스트에 담아 반환하세요.
    
    입력 예시: enemies = [-2, 3, 7, 12]
    출력 예시: [1, 1, 1, 1] (만약 모든 적이 음수라면 [3, 8, 18...] 등의 형태)"""

import heapq

def solve(enemies):
    # 1. 모든 적을 최대 힙에 담습니다. (가장 큰 체력부터 꺼내기 위해 마이너스 부호 사용)
    # 파이썬 heapq는 최소 힙이므로 -를 붙여 최대 힙처럼 동작하게 합니다.
    max_heap = []
    for e in enemies:
        heapq.heappush(max_heap, -e)
    
    answer = []
    current_selected = []
    
    # 2. k명을 하나씩 늘려가며 선택
    for k in range(1, len(enemies) + 1):
        # 이번 단계에서 가장 유리한 적 한 명을 힙에서 꺼내 현재 조합에 추가
        best_enemy = -heapq.heappop(max_heap)
        current_selected.append(best_enemy)
        
        # 3. 현재 선택된 k명의 적들을 '가장 유리한 순서'로 정렬 (내림차순)
        # 힙에서 꺼낸 순서대로 이미 정렬되어 있으므로, 
        # current_selected는 항상 내림차순이 유지됩니다.
        
        current_sum = 0
        min_prefix_sum = 0
        
        # 선택된 적들을 순차적으로 무찌르며 최저점 찾기
        for e_hp in current_selected:
            current_sum += e_hp
            if current_sum < min_prefix_sum:
                min_prefix_sum = current_sum
        
        # 최소 시작 체력 계산 (1 - 최저점)
        required_hp = max(1, 1 - min_prefix_sum)
        answer.append(required_hp)
        
    return answer

# 테스트 케이스
print(solve([-2, -5, -10])) # [3, 8, 18]
print(solve([-2, 3, 7, 12])) # [1, 1, 1, 1]

'CODINGTEST' 카테고리의 다른 글

[Java/Python] 타입 확인 및 replace  (0) 2026.05.08