1. 문제 / 난이도 : Level 4

Summer/Winter Coding(2019)

programmers.co.kr/learn/courses/30/lessons/62050

 

코딩테스트 연습 - 지형 이동

[[1, 4, 8, 10], [5, 5, 5, 5], [10, 10, 10, 10], [10, 10, 10, 20]] 3 15 [[10, 11, 10, 11], [2, 21, 20, 10], [1, 20, 21, 11], [2, 1, 2, 1]] 1 18

programmers.co.kr


2. 풀이 및 코드 설명

처음에는 대충 원하는 대로 구현했다가 참패... 20%빼고 런타임에러를 맞는 기적을 경험했다. 유력한 가설은 아마 시간초과나 메모리 초과가 아닐까...

그 다음 어떻게 짜야할 지 감이 잡히지 않아서 결국 검색의 도움을 빌렸다. 다른 분들의 풀이를 참고해서 아래와 같이 해결하였다.

 

1. 같은 그룹으로 묶일 수 있는 칸끼리 묶기 (BFS)

2. 각 그룹간의 이동 거리 구하기

3. 최소 이동 거리 구하기 (MST)

 

(1) 초기화 및 설정

N = len(land) 
check = [[0 for _ in range(N)] for _ in range(N)] # 각 칸의 그룹

(2) BFS

group = 1
    for i in range(N):
        for j in range(N):
            if not check[i][j]: # 아직 그룹이 정해지지 않은 칸이라면
                BFS(land, height, check, [i, j], group, N)
                group += 1
  • N*N의 칸을 모두 돌면서 아직 그룹이 정해지지 않은 칸이 있다면 해당 칸을 기점으로 BFS를 수행
def BFS(land, height, check, loc, group, N):
    direction = [(0, -1), (0, 1), (1, 0), (-1, 0)]
    q = deque()
    q.append(loc)
    check[loc[0]][loc[1]] = group

    while q:
        x, y = q.popleft()
        for dx, dy in direction:
            nx, ny = x + dx, y + dy
            if nx < 0 or nx >= N or ny < 0 or ny >= N or check[nx][ny]: continue
            if abs(land[nx][ny] - land[x][y]) <= height: # 이동하려는 칸과의 높이 차가 작다면
                q.append([nx, ny]) # 큐에 넣기
                check[nx][ny] = group # 그룹 표시

(3) 각 그룹간의 이동거리 계산

def find_ladder(check, land, N):
    direction = [(0, -1), (0, 1), (1, 0), (-1, 0)]
    ladders = defaultdict(lambda: math.inf) # 최대값으로 초기화
    
    for i in range(N):
        for j in range(N):
            current = check[i][j]
            for dx, dy in direction:
                nx, ny = i + dx, j + dy
                if nx < 0 or nx >= N or ny < 0 or ny >= N or check[nx][ny] == current: continue # 같은 그룹에 속해 있다면 건너뜀
                dist = abs(land[i][j] - land[nx][ny])
                ladders[(current, check[nx][ny])] = min(dist, ladders[(current, check[nx][ny])]) # 최소거리로 갱신
    return ladders
  • ladders : {(x, y): value} 형태로 되어있는 딕셔너리. x그룹에서 y그룹으로 이동할 때 필요한 사다리의 비용을 가지고 있음

 

(4) MST

def kruskal(ladders, group):
    sum = 0
    roots = {_:_ for _ in range(1, group)} # 정점들 root 표시
    for (x, y), value in ladders:
       if find_root(x, roots) != find_root(y, roots): # root가 같지 않을 경우
           union_find(x, y, roots) # root 연결
           sum += value # 값 갱신
       if len(roots.items()) == 1: return sum # 노드들이 전부 연결된 경우 리턴
    return sum
  • 크루스칼 알고리즘으로 MST 구현
  • Union-find를 적용해서 각 노드의 루트로 올라가 루트가 같지 않을 경우에 값을 갱신해주었다!

3. 전체 코드

from collections import deque, defaultdict
import math

def find_root(x, root):
    if x == root[x]: return x
    else:
        r = find_root(root[x], root)
        root[x] = r
        return r

def union_find(x, y, root):
    x_root = find_root(x, root)
    y_root = find_root(y, root)
    root[y_root] = x_root

def BFS(land, height, check, loc, group, N):
    direction = [(0, -1), (0, 1), (1, 0), (-1, 0)]
    q = deque()
    q.append(loc)
    check[loc[0]][loc[1]] = group

    while q:
        x, y = q.popleft()
        for dx, dy in direction:
            nx, ny = x + dx, y + dy
            if nx < 0 or nx >= N or ny < 0 or ny >= N or check[nx][ny]: continue
            if abs(land[nx][ny] - land[x][y]) <= height: 
                q.append([nx, ny])
                check[nx][ny] = group

def find_ladder(check, land, N):
    direction = [(0, -1), (0, 1), (1, 0), (-1, 0)]
    ladders = defaultdict(lambda: math.inf)
    
    for i in range(N):
        for j in range(N):
            current = check[i][j]
            for dx, dy in direction:
                nx, ny = i + dx, j + dy
                if nx < 0 or nx >= N or ny < 0 or ny >= N or check[nx][ny] == current: continue
                dist = abs(land[i][j] - land[nx][ny])
                ladders[(current, check[nx][ny])] = min(dist, ladders[(current, check[nx][ny])])
    return ladders

def kruskal(ladders, group):
    sum = 0
    roots = {_:_ for _ in range(1, group)}
    for (x, y), value in ladders:
       if find_root(x, roots) != find_root(y, roots):
           union_find(x, y, roots)
           sum += value
       if len(roots.items()) == 1: return sum
    return sum

def solution(land, height):
    answer = 0
    N = len(land)
    check = [[0 for _ in range(N)] for _ in range(N)]
    
    group = 1
    for i in range(N):
        for j in range(N):
            if not check[i][j]: 
                BFS(land, height, check, [i, j], group, N)
                group += 1
    
    ladders = find_ladder(check, land, N)
    ladders = sorted(ladders.items(),key=lambda x:x[1])
    
    answer = kruskal(ladders, group)
    return answer

1. 문제 / 난이도 : Gold Ⅳ

www.acmicpc.net/problem/2572

 

2572번: 보드게임

첫째 줄에 카드의 수 N이 주어진다. 둘째 줄에 N장의 카드의 색깔이 번호 순서대로 빈칸을 사이에 두고 주어진다. 셋째 줄에는 마을의 수 M과 길의 수 K가 빈칸을 사이에 두고 주어진다. 이어 K개

www.acmicpc.net


2. 풀이 및 코드 설명

처음에는 가능한 모든 경우의 수를 다 해봐야 하나...? 라고 생각했지만 K의 최대가 10000이고, N의 최대가 1000이어서 모든 경우의 수를 돌면 10000의 1000제곱....빠르게 포기했다. 다른 방법을 생각해보았다.

 

그래서 그 다음 생각한 방법이 DP를 이용해 푸는 것! 다음과 같은 입력이 들어올 때로 예를 들면,

5
R G R B G
4 5
1 2 R
1 3 G
2 3 G
1 4 R
4 3 B

다섯번째 G 카드를 들고 있을 때 각각의 마을에서 얻는 점수를 계산해보면, DP 배열은 다음과 같다

색상 \ 마을 1 2 3 4
R        
G        
R        
B        
G 10 10 10 0

그 다음, 네번째 B 카드를 들고 있을 때 1번 마을의 경우를 살펴보면 => 1번 마을에서는 2, 3, 4번 마을로 갈 수 있으므로

2번 마을로 가는 경우 = DP[5][2] + 0 = 10 + 0 = 10

3번 마을로 가는 경우 = DP[5][3] + 0 = 10 + 0 = 10

4번 마을로 가는 경우 = DP[5][4] + 0 = 0 + 0 = 0

세가지 경우의 MAX값은 10이다.

(* 빨간색 숫자는 해당 마을로 움직일 때 얻는 점수, B카드를 들고 있을 때 1번에서 2번 마을로 가는 경우에는 R색의 길을 지나므로 점수를 얻기 때문에 0을 더해준다)

 

마찬가지로 네번째 B 카드를 들고 있을 때 4번 마을의 경우를 살펴보면 => 4번 마을에서는 1, 3번 마을로 갈 수 있으므로

1번 마을로 가는 경우 = DP[5][1] + 0 = 10 + 0 = 10

3번 마을로 가는 경우 = DP[5][3] + 10 = 10 + 10 = 20

두가지 경우의 MAX값은 20이다.

 

위와 같은 과정을 거쳐 DP배열은 전부 채우면, DP는 다음과 같은 형태가 된다.

색상 \ 마을 1 2 3 4
R 40 40 30 40
G 30 30 40 30
R 30 20 20 20
B 10 10 10 20
G 10 10 10 0

원하는 것은 1번 마을에서 첫번째 카드를 들고 있을 때 얻을 수 있는 점수의 최댓값이므로 DP[1][R]이 답이된다.

 

(1) 설정 및 초기화

# 보드 정보 저장
for _ in range(K):
    i, j, c = sys.stdin.readline().split()
    board[int(i)].append((int(j), c))
    board[int(j)].append((int(i), c))
# dp 배열 초기화
dp = [[0 for _ in range(M + 1)] for _ in range(N+1)]

board[i] : i에서 갈 수 있는 정점과, 그 때 지나는 길의 색이 튜플 형태로 저장되어 있음

 

(2) DP배열 채우기

for i in range(N-1, -1, -1): # 카드를 하나씩 돌면서
    current_card = cards[i] # 현재 카드
    for j in range(M, 0, -1): # 각 마을을 하나씩 돌면서
        temp = 0
        for k in board[j]: # 해당 마을에서 갈 수 있는 정점의 정보
            node = k[0] # 정점 번호
            vertex = k[1] # 길의 색
            temp = max(dp[i+1][node] + (10 if vertex == current_card else 0), temp) # 모든 경우의 최댓값
        dp[i][j] = temp

 


3. 전체 코드

import sys

N = int(input())
cards = sys.stdin.readline().split()
M, K = map(int, sys.stdin.readline().split())
board = [[] for _ in range(M + 1)]

for _ in range(K):
    i, j, c = sys.stdin.readline().split()
    board[int(i)].append((int(j), c))
    board[int(j)].append((int(i), c))

dp = [[0 for _ in range(M + 1)] for _ in range(N+1)]

for i in range(N-1, -1, -1):
    current_card = cards[i]
    for j in range(M, 0, -1):
        temp = 0
        for k in board[j]:
            node = k[0]
            vertex = k[1]
            temp = max(dp[i+1][node] + (10 if vertex == current_card else 0), temp)
        dp[i][j] = temp
print(dp[0][1])

1. 문제 / 난이도 : Gold Ⅴ

www.acmicpc.net/problem/1092

 

1092번: 배

첫째 줄에 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄에는 각 크레인의 무게 제한이 주어진다. 이 값은 1,000,000보다 작거나 같다. 셋째 줄에는 박스의 수 M이 주어진다. M은 10,000보

www.acmicpc.net


2. 풀이 및 코드 설명

하... 분명히 틀린 부분이 없는데 계속 시간초과가 나서 PyPy3로 제출해보니 맞았다...앞으로는 PyPy3로 제출하자....

무거운 화물을 들 수 있는 크레인에게 무거운 화물을 옮기게 하자! 만 기억하면 쉽게 풀리는 문제. 무게 제한이 높은 크레인에게 현재 남아 있는 화물 중 가장 무거운 것을 들게 해야 모든 박스를 옮기는 최소 시간이 나온다.

 

간단한 코드이기 때문에 코드 블락 단위 설명은 생략.


3. 전체 코드

import sys
N = int(input())
limits = map(int, sys.stdin.readline().split()) # 크레인 별 무게 제한
M = int(input())
packages = map(int, sys.stdin.readline().split()) # 화물 별 무게

# 무게 제한과 화물 무게 전부 내림차순으로 정렬
limits = sorted(limits, reverse=True)
packages = sorted(packages, reverse=True)

# 무게 제한이 제일 높은 크레인도 제일 무거운 화물을 들 수 없는 경우
if packages[0] > limits[0] : 
    print(-1)
    exit()

answer = 0
# 화물이 전부 옮겨질 때까지
while len(packages) > 0:
    answer += 1
    # 무게제한을 돌면서 옮길 수 있는 화물을 옮김
    for l in limits:
        for j in range(len(packages)):
            if l >= packages[j]: # 화물을 옮길 수 있으면
                del packages[j]
                break
print(answer)

1. 문제 / 난이도 : Level 2

programmers.co.kr/learn/courses/30/lessons/42885

 

코딩테스트 연습 - 구명보트

무인도에 갇힌 사람들을 구명보트를 이용하여 구출하려고 합니다. 구명보트는 작아서 한 번에 최대 2명씩 밖에 탈 수 없고, 무게 제한도 있습니다. 예를 들어, 사람들의 몸무게가 [70kg, 50kg, 80kg, 5

programmers.co.kr


2. 풀이 및 코드 설명

모든 사람들을 몸무게 순으로 정렬했을 때, 가장 몸무게가 가벼운 사람 + 가장 몸무게가 무거운 사람이 무게 제한보다 크다면 가장 몸무게가 무거운 사람은 아무와도 구명보트를 같이 탈 수 없다.

따라서, 그 경우에는 구명보트의 카운트를 1 증가 시켜주고 가장 몸무게가 가벼운 사람 + 2번째로 가장 몸무게가 무거운 사람과 무게제한을 비교하는 식으로 계속 반복해나가면 답을 얻을 수 있다.

 

간단한 코드라, 코드 블럭 단위의 설명은 생략.


3. 전체 코드

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;


int solution(vector<int> people, int limit) {
	int answer = 0;
	sort(people.begin(), people.end()); // people 정렬
	int lo = 0; // 가장 몸무게가 가벼운 사람의 index
	int hi = people.size() - 1; // 가장 몸무게가 무거운 사람의 index

	while (lo <= hi) {
		if (people[lo] + people[hi] <= limit) { // 가장 몸무게가 가벼운 사람과 가장 무거운 사람이 같이 구명보트를 탈 수 있으므로
			lo++; 
			hi--;
		}
		else { // 그 외에는 가장 무거운 사람 혼자 구명보트를 타야 함
			hi--;
		}
		answer++;
	}

	return answer;
}

+ Recent posts