⚙️ 알고리즘/문제풀이

[Python] 프로그래머스 숫자게임, 백준 청소년 상어

dev_zoe 2025. 7. 9. 16:54
반응형

1. 문제 - 프로그래머스 숫자게임

https://school.programmers.co.kr/learn/courses/30/lessons/12987

 

💡 문제 접근 방식

본래라면 A팀의 각 원소마다 더 큰 원소들을 찾아 그 합계를 구하려면, 이중 반복문이 되지만 A와 B는 1이상 100,000 이하이기 때문에 시간초과가 발생한다.

따라서 기존 시간초과 발생 코드를 O(N)을 통해 효율적으로 찾을 수 있게 해주는 투포인터 알고리즘을 사용한다.

 

✅ 풀이

def solution(A, B):
    A.sort()     # 차례대로 움직이며 B가 A보다 더 큰 원소를 찾아야하므로, 둘다 오름차순으로 정렬한다.
    B.sort()
    answer = 0
    
    idx_A, idx_B = 0, 0
    while idx_A < len(A) and idx_B < len(B):
        if A[idx_A] < B[idx_B]:
            idx_A += 1   # B가 더 큰 인덱스를 찾았으므로 그 다음 수를 비교하기 위해 idx_A, idx_B 둘다 + 1
            idx_B += 1
            answer += 1
        else:    # B가 더 큰 인덱스를 찾아다니기 위해 idx_B만 + 1
            idx_B += 1
    
    return answer

 

2. 문제 - 백준 청소년 상어 (삼성 SW 역량테스트 기출)

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

 

💡 문제 접근 방식

 

기본적으로 시뮬레이션/구현 문제이다. 그런데 "상어가 먹을 수 있는 물고기 번호의 합의 최댓값을 구해보자." 에서 어떻게 최대값을 구할 수 있을지에서 아이디어가 막혔다.

다른 사람의 풀이를 찾아보니 DFS를 사용하여 풀이할 수 있고, 문제에서 어떻게 DFS 키워드를 잡고 접근할 수 있을지 고민해보았다.

 

1) "청소년 상어는 (0, 0)에 있는 물고기를 먹고, (0, 0)에 들어가게 된다."

- 시작점이 정해져있음. 그러나 여기까지는 단순 시뮬레이션 문제로 보일 수 있다

 

2) “상어는 현재 방향으로 1칸 이상 이동 가능하며, 물고기가 있는 칸만 이동 가능하다.”

- 현재 있는 칸 마다 선택지가 여러개 선택할 수 있다 --> 가지치기 (상태분기) --> 그래프탐색 유추

 

3) 상어가 이동할 수 있는 칸이 없으면 공간에서 벗어나 집으로 간다.

이 과정이 반복될때, 상어가 먹을 수 있는 물고기 번호 합의 최대값을 구해보자.

- 상어가 더이상 이동할 수 없는 칸까지 이동함 = 깊이 우선 탐색

- 이 때, 최대값을 보장할 수 없으므로 이전 상태를 복구하여 다시 탐색해야함 = 백트래킹

 

✅ 나의 풀이

import copy

dx = [-1, -1, 0, 1, 1, 1, 0, -1]
dy = [0, -1, -1, -1, 0, 1, 1, 1]  # 1부터 8까지 차례대로 방향 offset 배열
answer = 0

fishes = []            # 물고기 번호 적힌 보드
f_directions = []      # 물고기 번호마다 움직이는 방향 적힌 보드

for _ in range(4):     # 입력받기
  temp = list(map(int, input().split()))
  fish = []
  dir = []
  for j in range(8):
    if j%2 == 0:
      fish.append(temp[j])
      if len(fish) == 4:
        fishes.append(fish)
        fish = []
    else:
      dir.append(temp[j]-1)    
      if len(dir) == 4:
        f_directions.append(dir)
        dir = []

def find_coord(board, num):    # 물고기 좌표 찾기
  for i in range(4):
    for j in range(4):
      if board[i][j] == num:
        return i, j

  return -1, -1   # 못찾은 경우

def moving_fishes(board, directions, shark_x, shark_y):
  for i in range(1, 17):  # 번호가 작은 물고기부터 이동한다.
    x, y = find_coord(board, i)   # 물고기 좌표 찾기
    
    if x == -1 and y == -1:    # 이미 먹힌 물고기이므로, 다음 물고기의 이동 작업을 시작한다.
      continue
      
    dir = directions[x][y]
      
    for j in range(8): 
      # 이동할 수 있는 칸을 찾을 때까지 45도 반시계 회전시킨다.
      nd = (dir + j) % 8
      nx = x + dx[nd]
      ny = y + dy[nd]

      # 이동할 수 없는 칸: 경계를 벗어나거나, 상어가 있거나
      if not (0<=nx<4 and 0<=ny<4): 
        continue

      if nx == shark_x and ny == shark_y:
        continue

      # 이동할 수 있는 칸이 있다면, 물고기와 방향 포함해서 같이 위치 바뀜
      board[nx][ny], board[x][y] = board[x][y], board[nx][ny]
      directions[x][y] = nd  # 이동할 수 있는 방향으로 갱신한 다음,
      directions[x][y], directions[nx][ny] = directions[nx][ny], directions[x][y]   # 서로 바꿔주기
      break    # 이동할 수 있는 칸을 찾았으므로 break
      
def positions_shark_can_go(board, shark_x, shark_y, dir):
  positions = []
  
  for _ in range(3):   # 상어는 방향에 있는 칸으로 이동할 수 있고, 한번에 여러개의 칸을 이동할 수 있다.
    shark_x += dx[dir]
    shark_y += dy[dir]

    if 0<=shark_x<4 and 0<=shark_y<4 and board[shark_x][shark_y] != 0:        # 물고기가 없는 칸으로는 이동할 수 없다.
      positions.append((shark_x, shark_y))

  return positions

def dfs(x, y, board, directions, total):
  global answer

  # 상태마다 board의 상태가 바뀌므로 복사 필요함
  copied = copy.deepcopy(board)
  copied_directions = copy.deepcopy(directions)
  
  total += copied[x][y]   # 물고기를 먹음
  copied[x][y] = 0        # 먹는 상태로 처리

  moving_fishes(copied, copied_directions, x, y)   # 물고기 움직임

  positions = positions_shark_can_go(copied, x, y, copied_directions[x][y])

  if not positions:           # 더이상 이동할 수 없다면 answer 갱신하고 되돌아가기
    answer = max(answer, total)
    return

  for nx, ny in positions:   # 이동할 수 있는 모든 칸에 대해 탐색 진행
    dfs(nx, ny, copied, copied_directions, total)

# 0, 0에서부터 시작함
dfs(0, 0, fishes, f_directions, 0)

print(answer)

 

여기서 물고기의 방향과 함께 입력받고 처리하는 부분을 개선할 수 있다면 다음과 같이 개선할 수 있다.

board를 3차원 배열로 만들어 처리하여 개선하면 코드가 다음과 같다.

fishes = []          # 입력받기
for _ in range(4):
  temp = list(map(int, input().split()))
  row = []
  for j in range(0, 8, 2):
    a, b = temp[j : j + 2]
    row.append([a, b - 1])   # 물고기 번호, 방향
  fishes.append(row)

def find_coord(board, num):    # 물고기 좌표 찾기
  for i in range(4):
    for j in range(4):
      if board[i][j][0] == num:
        return i, j

  return -1, -1   # 못찾은 경우

def moving_fishes(board, shark_x, shark_y):
  for i in range(1, 17):  # 번호가 작은 물고기부터 이동한다.
    x, y = find_coord(board, i)   # 물고기 좌표 찾기
    
    if x == -1 and y == -1:    # 이미 먹힌 물고기이므로, 다음 물고기의 이동 작업을 시작한다.
      continue
      
    dir = board[x][y][1]    # 기존 방향
      
    for j in range(8): 
      # 이동할 수 있는 칸을 찾을 때까지 45도 반시계 회전시킨다. 처음 방향도 가능할 수 있으므로 j는 0부터 시작한다.
      nd = (dir + j) % 8
      nx = x + dx[nd]
      ny = y + dy[nd]

      # 이동할 수 없는 칸: 경계를 벗어나거나, 상어가 있거나
      if not (0<=nx<4 and 0<=ny<4): 
        continue

      if nx == shark_x and ny == shark_y:
        continue

      # 이동할 수 있는 칸이 있다면, 물고기와 방향 포함해서 같이 위치 바뀜
      board[x][y][1] = nd  # 주의!!: 이동할 수 있는 방향으로 갱신한 다음,
      board[nx][ny], board[x][y] = board[x][y], board[nx][ny]
      break    # 이동할 수 있는 칸을 찾았으므로 break

def dfs(x, y, board, total):
  global answer

  # 상태마다 board의 상태가 바뀌므로 복사 필요함
  copied = copy.deepcopy(board)
  
  total += copied[x][y][0]   # 물고기를 먹음
  copied[x][y][0] = 0        # 먹는 상태로 처리

  moving_fishes(copied, x, y)   # 물고기 움직임

  positions = positions_shark_can_go(copied, x, y, copied[x][y][1])


... 이하 동일

 

반응형