https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5V4A46AdIDFAWu&& 

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

swexpertacademy.com


풀이 과정

문제의 조건에 맞게 주어진 벌통을 선택할 때, 선택한 벌통내에서 채취할 수 있는 가치의 최대값을 구하는 문제이다.

 

일꾼이 벌통을 선택하는 경우를 중복 순열의 경우의 수를 만들어 구하였고, 각 벌통의 칸에서 꿀을 퍼갈지 안퍼갈지를 부분집합의 경우의 수를 만들어 구하였다.

 

N, M이 크지 않은 수로 주어지므로 완전 탐색으로 풀 수 있음을 추측할 수 있다. 로직을 세우고 시간복잡도를 계산해도 문제에서 주어진 시간을 초과하지 않음을 알 수 있다. 설명은 코드에 주석으로 달아놓았다.


소스 코드

def perm(index):  # 첫번째 벌꿀통의 r, c 두번째 벌꿀통의 r, c를 중복순열로 생성
    if index == 4:
        check()  # 값을 구하러 간다
        return

    for i in range(N):
        numbers.append(i)  # 이번 수를 중복 순열에 넣고
        perm(index+1)  # 다음 index의 수를 고르러 간다
        numbers.pop()  # 이번 수를 중복 순열에 넣지 않고 이번 index에 넣을 다음 수를 찾으러 간다


def check():
    global answer, max1
    a, b, c, d = numbers[0], numbers[1], numbers[2], numbers[3]
    if b > N-M or d > N-M:  # M칸 크기의 벌꿀 통을 가로줄에 넣을 수 없는 경우 진행하지 않는다
        return
    selected = [[False for _ in range(N)] for _ in range(N)]  # 벌꿀통이 놓인 곳을 표시
    subset_selected = [[False for _ in range(N)] for _ in range(N)]  # 벌꿀통 안에서 벌꿀을 퍼갈 공간 표시
    for i in range(M):
        selected[a][b+i] = True  # 벌꿀통을 놓았다고 표시한다
    max1 = 0
    subset(a, b, 0, subset_selected)  # 벌꿀통 안에서 퍼간 벌꿀의 양이 C를 넘지 않으면서 최대 가치를 지니는 경우가 언제인지
    # 부분집합으로 모든 경우를 만들어서 판단한다
    max2 = max1

    for i in range(M):
        if selected[c][d+i]:  # 2번째 벌꿀 통을 놓는데 1번째 벌꿀 통이랑 겹치면 진행하지 않는다
            return
        selected[c][d+i] = True
    max1 = 0
    subset(c, d, 0, subset_selected)  # 2번째 벌꿀 통 안에서 1번째 벌꿀통 부분집합 만들기에서 했던 것과 동일 행위 진행

    answer = max(answer, max1 + max2)  # 정답 갱신


def subset(i, j, index, subset_selected):  # 부분집합 경우의 수 생성 코드
    global max1
    if index == M:  # 모든 벌꿀통의 칸에 대해 진행했으면
        temp = 0
        values = 0
        for k in range(M):
            if subset_selected[i][j+k]:  # 선택된 칸의
                temp += board[i][j+k]  # 양의 누적을 저장하고
                values += board[i][j+k]**2  # 가치의 누적을 저장한다
        if temp > C:  # 양이 C를 넘었으면 담을 수 없으므로 진행하지 않는다
            return
        max1 = max(max1, values)  # 양이 C를 넘지 않았다면 가치 누적값의 최대를 갱신한다
        return

    subset_selected[i][j + index] = True  # 벌꿀통의 특정 칸을 고르는 경우
    subset(i, j, index+1, subset_selected)  # 재귀 진행
    subset_selected[i][j+index] = False  # 벌꿀통의 특정 칸을 고르지 않는 경우
    subset(i, j, index + 1, subset_selected)  # 재귀 진행


T = int(input())

for tc in range(1, T+1):
    max1 = 0
    N, M, C = map(int, input().split())
    board = [list(map(int, input().split())) for _ in range(N)]  # 2차원 배열 1줄로 받는 코드
    answer = 0  # 정답을 저장할 변수
    numbers = []  # 중복순열을 생성할 코드
    perm(0)  # 중복순열 재귀
    print(f"#{tc} {answer}")  # 정답 출력

 

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

 

1406번: 에디터

첫째 줄에는 초기에 편집기에 입력되어 있는 문자열이 주어진다. 이 문자열은 길이가 N이고, 영어 소문자로만 이루어져 있으며, 길이는 100,000을 넘지 않는다. 둘째 줄에는 입력할 명령어의 개수

www.acmicpc.net


풀이 과정

문자열이 주어지고, 문자열 안에서 커서를 왼쪽, 오른쪽으로 이동시키며 커서 왼쪽의 문자를 추가하거나 삭제시키는 연산을 구현하는 문제이다.

 

문자열을 배열로 생각한다면 문자열에서 문자를 추가하거나 삭제하는 연산은 O(N)의 시간복잡도를 가진다. 추가된 문자 뒤쪽에 있는 문자를 한 칸씩 밀거나, 삭제된 문자 뒤쪽에 있는 문자들을 한 칸씩 당겨야 하기 때문이다. 따라서 문자의 추가와 삭제가 O(1)에 이루어질 수 있는 링크드 리스트를 사용해 문제를 해결해야 한다.

 

이 문제는 추가, 삭제 연산이 입력으로 들어오는 index에서 이루어 지는 것이 아닌, 커서 왼쪽에서 이루어진다는 것이 고정되어 있으므로 2개의 스택을 활용하여, 왼쪽 스택에는 커서 왼쪽에 있는 문자들을 저장, 오른쪽 스택에는 커서 오른쪽에 있는 문자들을 저장하는 방법으로 문제를 해결할 수도 있다.


소스 코드

2개의 스택 - 자바

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.StringTokenizer;

public class Main { // 메모리 : 142208KB / 시간 608ms
	static StringBuilder sb = new StringBuilder();
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		ArrayDeque<Character> lStack = new ArrayDeque<>(); // 커서 왼쪽에 있는 문자들을 lStack에 저장한다
		ArrayDeque<Character> rStack = new ArrayDeque<>(); // 커서 오른쪽에 있는 문자들을 rStack에 저장한다
		char[] str = br.readLine().toCharArray();
		
		for (int i = 0; i < str.length; i++) {
			lStack.addLast(str[i]); // 초기에는 커서가 문자열의 가장 오른쪽에 있으므로 모든 문자를 lStack에 넣는다
		}
		
		int N = Integer.parseInt(br.readLine());
		
		for (int i = 0; i < N; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			char temp = st.nextToken().charAt(0);
			
			switch (temp) {
			case 'L': // L이 들어오면 커서를 왼쪽으로 한 칸 옮긴다
//				커서 왼쪽에 있는 문자 개수가 줄고, 커서 오른쪽에 있는 문자 개수가 하나 느는것과 같다
//				lStack의 원소를 rStack으로 하나 옮겨준다
				if (!lStack.isEmpty()) rStack.addLast(lStack.pollLast());
				break;
			case 'D': // D가 들어오면 커서를 오른쪽으로 한 칸 옮긴다
//				커서 오른쪽에 있는 문자 개수가 줄고, 커서 왼쪽에 있는 문자 개수가 하나 느는것과 같다
//				rStack의 원소를 lStack으로 하나 옮겨준다
				if (!rStack.isEmpty()) lStack.addLast(rStack.pollLast());
				break;
			case 'B': // B가 들어오면 커서 왼쪽에 있는 문자가 하나 없어진다
				if (!lStack.isEmpty()) lStack.pollLast(); // lStack에서 원소를 하나 제거해준다
				break;
			case 'P': // P가 들어오면 다음 오는 문자를 커서 왼쪽에 붙인다
				char temp2 = st.nextToken().charAt(0);
				lStack.addLast(temp2); // 다음 오는 문자를 lStack에 하나 넣어준다
				break;
			}
		}
		
		
//		lStack의 모든 문자를 rStack으로 옮긴 후, rStack의 모든 문자를 pop() 하면서 출력해주면 문자열을 출력할 수 있다
		while (!lStack.isEmpty()) {
			rStack.addLast(lStack.pollLast());
		}
		
		while (!rStack.isEmpty()) {
			sb.append(rStack.pollLast());
		}
		
		System.out.print(sb.toString());
	} // end of main
} // end of class

 

2개의 스택 - 파이썬

import sys
input = sys.stdin.readline

# 메모리 38664KB / 시간 620ms

l_stack = list(map(str, input().rstrip()))
r_stack = []
N = int(input())

for i in range(N):
    command = input().split()
    if command[0] == "L":
        if len(l_stack) != 0:
            r_stack.append(l_stack.pop())
    elif command[0] == "D":
        if len(r_stack) != 0:
            l_stack.append(r_stack.pop())
    elif command[0] == "B":
        if len(l_stack) != 0:
            l_stack.pop()
    elif command[0] == "P":
        l_stack.append(command[1])

while len(l_stack) > 0:
    r_stack.append(l_stack.pop())

while len(r_stack) > 0:
    print(r_stack.pop(), end="")

 

링크드 리스트 - 자바

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.StringTokenizer;

public class Main { // 메모리 118980KB / 시간 624ms
	static StringBuilder sb = new StringBuilder();
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		char[] str = br.readLine().toCharArray();
		int N = str.length;
		LinkedList<Character> ll = new LinkedList<>();
		
		for (int i = 0 ; i < N; i++) {
			ll.add(str[i]);
		}
		
		ListIterator<Character> iter = ll.listIterator(); // 리스트에서 커서 위치를 저장할 ListIterator을 만든다
		
		while (iter.hasNext()) { // 
			iter.next(); // iter에 대입해줄 필요 없이 알아서 iter를 다음으로 넘겨줌
		}
		
		int M = Integer.parseInt(br.readLine());
		
		for (int i = 0; i < M; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			char temp = st.nextToken().charAt(0);
			switch (temp) {
			case 'L':
				if (iter.hasPrevious()) iter.previous(); // 링크드리스트에 이전 노드가 있으면 그 노드를 가리키도록 함
				break;
			case 'D':
				if (iter.hasNext()) iter.next(); // 링크드리스트에 다음 노드가 있으면 그 노드를 가리키도록 함
				break;
			case 'B':
				if (iter.hasPrevious()) { // 링크드 리스트에 이전 노드가 있으면 그 노드를 가리킨 뒤 지움
					iter.previous();
					iter.remove();
				}
				break;
			case 'P':
				char temp2 = st.nextToken().charAt(0);
				iter.add(temp2); // 링크드의 현재 가리키고 있는 노드의 index에 temp2가 오도록 추가함
				break;
			}
		}
		
		for (char c : ll) {
			sb.append(c);
		}
		
		System.out.print(sb.toString());
		
	} // end of main
} // end of class

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

 

2304번: 창고 다각형

첫 줄에는 기둥의 개수를 나타내는 정수 N이 주어진다. N은 1 이상 1,000 이하이다. 그 다음 N 개의 줄에는 각 줄에 각 기둥의 왼쪽 면의 위치를 나타내는 정수 L과 높이를 나타내는 정수 H가 한 개의

www.acmicpc.net


풀이 과정

지붕의 수평 부분이 어떤 기둥의 윗면, 지붕의 수직 부분이 어떤 기둥의 옆면과 닿아야 하고, 지붕에 오목한 부분이 있으면 안될 때 창고 다각형의 최소 값을 구하는 문제이다.

 

지붕의 높이가 높았다가, 낮았다가, 다시 높아지는 경우에 지붕에 오목한 부분이 생기게 된다. 막대의 높이가 가장 높은 곳을 T라 했을 때, 지붕의 시작점부터 T까지는 지붕의 높이가 높아지기만 해야 하고, T부터 지붕의 끝까지는 지붕의 높이가 낮아지기만 해야 한다. 그렇지 않으면 오목한 부분이 생기게 될 것이다.

 

막대의 길이를 쭉 입력 받고, 막대의 높이가 가장 높은 곳 T를 기준으로 반씩 나눠서, 지붕의 시작부터 T까지는 지붕의 높이가 점점 높아지게, T부터 지붕의 끝 까지는 지붕의 높이가 점점 낮아지게끔 막대를 보고 지붕의 높이를 설정해주면 지붕에 오목한 부분이 생기지 않게끔 창고 다각형의 최소 값을 구할 수 있다.


소스 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main { // 메모리 11980kb / 시간 84ms
    static StringBuilder sb = new StringBuilder();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        // 가장 큰 막대를 기준으로 절반 나눠서 진행할 것이므로 가장 높은 막대의 위치, 막대가 처음 나오는 위치, 막대가 마지막으로
        // 나오는 위치를 저장할 변수들을 선언하였다
        int maxHeight = Integer.MIN_VALUE;
        int maxSpace = Integer.MIN_VALUE;
        int finalSpace = Integer.MIN_VALUE;
        int startSpace = Integer.MAX_VALUE;

        int[] board = new int[1001]; // 4kb정도?  L이 1000이하까지 들어오므로 인덱스 편하게 쓰려고 1001까지 잡았다

        for (int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int L = Integer.parseInt(st.nextToken());
            int H = Integer.parseInt(st.nextToken());
            board[L] = H; // 보드에 저장

            if (maxHeight < H) { // 가장 높은 막대의 높이와 위치를 저장한다
                maxHeight = H;
                maxSpace = L;
            }

            startSpace = Math.min(L, startSpace); // 막대가 시작되는 위치를 구한다
            finalSpace = Math.max(L, finalSpace); // 막대가 끝나는 위치를 구한다
        }

        int maxTemp = Integer.MIN_VALUE;
        
        // 일단 가장 긴 막대 길이를 더한다
        int answer = maxHeight;

        // 시작지점부터 높은 막대 위치 직전까지 점검하면서 점검한 막대 길이중 가장 긴 길이를 더해 나간다
        for (int i = startSpace; i < maxSpace; i++) {
            maxTemp = Math.max(board[i], maxTemp);
            answer += maxTemp;
        }

        maxTemp = Integer.MIN_VALUE;
        
        // 끝 지점부터 높은 막대 위치 직후까지 점검하면서 점검한 막대 길이중 가장 긴 길이를 더해 나간다
        for (int i = finalSpace; i > maxSpace; i--) {
            maxTemp = Math.max(board[i], maxTemp);
            answer += maxTemp;
        }

        System.out.println(answer);
    } // end of main
}// end of class

 

import sys
input = sys.stdin.readline

# 메모리 31256kb / 시간 44ms

N = int(input())
board = [0 for _ in range(1001)]
maxSpace = 0
maxHeight = 0
startSpace = 1001
finalSpace = -1

for i in range(N):
    L, H = map(int, input().split())
    board[L] = H
    startSpace = min(startSpace, L)
    finalSpace = max(finalSpace, L)

    if maxHeight < H:
        maxHeight = H
        maxSpace = L

answer = maxHeight
max_temp = 0

for i in range(startSpace, maxSpace):
    max_temp = max(max_temp, board[i])
    answer += max_temp

max_temp = 0

for i in range(finalSpace, maxSpace, -1):
    max_temp = max(max_temp, board[i])
    answer += max_temp

print(answer)

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

 

10972번: 다음 순열

첫째 줄에 입력으로 주어진 순열의 다음에 오는 순열을 출력한다. 만약, 사전순으로 마지막에 오는 순열인 경우에는 -1을 출력한다.

www.acmicpc.net


풀이 과정

순열이 주어졌을 때, 사전순으로 다음에 오는 순열을 구하는 문제이다.

 

C++에서는 next_permutation 함수를 통해 다음 순열을 구할 수 있지만, 자바와 파이썬에서는 다음 순열을 구하는 내장 함수가 존재하지 않으므로 직접 다음 순열을 구하는 알고리즘을 구현해줘야 한다.

 

다음 순열을 구하는 알고리즘은 다음과 같은 4단계로 이루어진다.

1. a[i-1] < a[i]인 가장 큰 i를 찾는다.

2. a[i-1] < a[j]인 가장 큰 j를 찾는다.

3. a[i-1]과 a[j]를 교체한다

4. a[i]부터 끝까지 뒤집어준다

 

C++에서는 swap함수가 있고 파이썬은 한 줄로 스왑이 가능한데 함수를 따로 만들거나 보조 변수를 하나 두거나 해야 해서 코드가 좀 길어지는 감이 있는 것 같다.


소스 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
	static StringBuilder sb = new StringBuilder();
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int N = Integer.parseInt(br.readLine());
		int[] arr = new int[N];
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		for (int i = 0; i < N; i++) {
			arr[i] = Integer.parseInt(st.nextToken());
		}
		
		int a = N-1, b = N-1;
		
		while (a > 0 && arr[a-1] > arr[a]) { // arr[i-1] < a[i]인 가장 큰 i를 탐색한다
			a--;
		}
		
		if (a == 0) {
			System.out.println(-1);
			return;
		}
		
		while (b > 0 && arr[a-1] > arr[b]) { // arr[i-1] < a[j]인 가장 큰 j를 탐색한다
			b--;
		}
		
// arr[i-1]과 a[j]의 값을 바꾼다
		int temp = arr[a-1];
		arr[a-1] = arr[b];
		arr[b] = temp;
		b = N-1;
		
// arr[i]부터 모든 순서를 뒤집는다
		while (a < b) {
			temp = arr[a];
			arr[a] = arr[b];
			arr[b] = temp;
			a++; b--;
		}
		
		
		
		for (int i = 0; i < N; i++) {
			sb.append(arr[i]).append(" ");
		}
		
		System.out.print(sb.toString());
	} // end of main
} // end of class

 

import sys
input = sys.stdin.readline

N = int(input())
a = list(map(int, input().split()))
i = len(a) - 1

while i > 0 and a[i-1] >= a[i]:  # a[i-1] < a[i]인 가장 큰 i를 찾는다
    i -= 1

if i <= 0:
    print(-1)
    exit(0)

j = len(a) - 1

while a[i-1] >= a[j]:  # a[i-1] < a[j]인 가장 큰 j를 찾는다
    j -= 1

a[i-1], a[j] = a[j], a[i-1]  # a[i-1]과 a[j]를 교체한다
j = len(a) - 1

while i < j:  # a[i]부터 끝까지 순열을 뒤집는다
    a[i], a[j] = a[j], a[i]
    i += 1
    j -= 1

print(' '.join(map(str, a)))  # a를 출력한다

 

https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV140YnqAIECFAYD 

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

swexpertacademy.com


풀이 과정

완전 이진 트리가 주어질 때 중위 순회의 결과값을 출력하는 문제이다.

 

완전 이진 트리의 루트를 1번 노드라고 하면, 배열로 트리를 표현할 수 있다.

i번째 노드의 왼쪽 자식 노드는 2i, 오른쪽 자식 노드는 2i+1, 부모 노드는 i/2로 저장하면 배열로 트리의 표현이 가능하다.

 

전위 순회는 자신 -> 왼쪽 자식 -> 오른쪽 자식

중위 순회는 왼쪽 자식 -> 자신 -> 오른쪽 자식

후위 순회는 왼쪽 자식 -> 오른쪽 자식 -> 자신

 

순으로 순회한다. 보통 전, 중, 후위 순회를 구현할 때 재귀를 이용하는데, 재귀문의 위치를 조금만 바꿔주면 3가지의 순회를 쉽게 구현할 수 있다.


소스 코드

import sys
input = sys.stdin.readline


def dfs(cur):
    if cur > n: # 트리 크기보다 현재 방문점이 커지면 종료
        return
    dfs(cur*2) # 왼쪽 자식 노드 방문
    print(temp[cur][1], end="") # 현재 노드 출력
    dfs(cur*2+1) # 오른쪽 자식 노드 방문
    #왼쪽 -> 현재 -> 오른쪽 순으로 방문하는 중위 순회이며 위의 3줄의 코드의 위치에 따라
    #전위, 중위, 후위 순회가 결정된다


for tc in range(1, 11):
    n = int(input())
    temp = [0] # 0번 노드는 안쓸거니까 비워둔다

    for i in range(n):
        temp.append(list(map(str, input().split()))) # 입력

    print(f"#{tc} ", end="")
    dfs(1) # 1번 노드부터 재귀에 들어간다
    print()

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이 과정

여러개의 사각형이 주어질 때, 시작점에서 도착지점까지 사각형의 모서리만 타고 이동할 때의 최소 이동 횟수를 구하는 문제이다.

 

문제에서 주어진 예시와 같이 사각형의 내부는 방문할 수 없다.

 

입력값을 확인하면서 사각형의 모서리는 board값 1, 사각형 내부는 2, 내부와 모서리가 겹치는 경우는 내부가 우선해서 모서리 처리 안되게끔 board값을 처리하고 이동 가중치가 전부 1이니까 BFS로 board를 탐색하면서 최소 이동 거리를 구하면 답이 나올 것 같다. 하지만 이렇게 해결시 몇몇 테스트 케이스에서 오류가 발생한다.

 

위의 그림에서 (x, y) = (3, 5)인 지점을 보자. (4, 5)도 방문이 가능하지만 (3, 6)도 거리가 1만큼 떨어저 있고 사각형의 모서리에 해당하므로 방문이 가능하다고 판단해서 방문해버린다. 실제로는 (3, 6)을 방문하려면 (4, 5), (4, 6)을 먼저 방문해야 함에도 불구하고 말이다.

 

ㄷ자 모양의 모서리부분이 존재할 시, 바로 방문 불가능한 점을 방문이 가능하다고 처리하는 문제가 발생하는 것이다. 가장 간단한 해결방법은 좌표계를 2배 확대해서 보는 것이다. (1, 1)과 (1, 2) 사이에 2칸이 있다고 생각하는 것이다.

 

좌표계를 2배로 잡아서 보고 시작점, 도착지점 다 2배씩 해준 다음에 마지막에 구한 최소 이동 횟수도 2로 나눠주면 ㄷ자 모양의 모서리에서도 오류가 발생하지 않고 정상적으로 답을 구할 수 있다.


소스 코드

import collections

def solution(rectangle, characterX, characterY, itemX, itemY):
    board = [[0 for _ in range(102)] for _ in range(102)]
    for rec in rectangle:
        a, b, c, d = rec
        a *= 2; b *= 2; c *= 2; d *= 2
        for y in range(b, d+1):
            for x in range(a, c+1):
                if board[y][x] == 2:
                    continue
                if y == b or y == d or x == a or x == c:
                    board[y][x] = 1
                else:
                    board[y][x] = 2
    
    def bfs(start_y, start_x):
        dy = [0, 1, 0, -1]
        dx = [1, 0, -1, 0]
        discovered = [[False for _ in range(102)] for _ in range(102)]
        Q = collections.deque()
        Q.append([start_y, start_x, 0])
        discovered[start_y][start_x] = True
        
        while Q:
            cur_y, cur_x, dist = Q.popleft()
            if cur_y == itemY*2 and cur_x == itemX*2:
                return dist // 2
            for i in range(4):
                ny = cur_y + dy[i]
                nx = cur_x + dx[i]
                if 1 <= ny <= 100 and 1 <= nx <= 100 and board[ny][nx] == 1 and not discovered[ny][nx]:
                    Q.append([ny, nx, dist+1])
                    discovered[ny][nx] = True
        
    answer = bfs(characterY*2, characterX*2)
    return answer

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이 과정

ICN에서 시작해 주어진 비행기 표를 모두 사용하는 여행 경로를 짜는 문제이다. 가능한 경로가 여러개라면 사전식으로 앞선 경로를 답으로 내야 한다.

 

모든 도시를 방문할 수 없는 입력은 들어오지 않으므로 모든 도시를 DFS로 방문하면 된다. 비행기 표를 defaultdict에 담아놓는데, pop으로 비행기표를 꺼내면서 알파벳 순이 앞선 도시를 먼저 방문하기 위해 티켓 리스트를 도착지를 기준으로 알파벳 내림차순으로 정렬해놓고 defaultdict에 담았다.

 

https://greentea31.tistory.com/202

 

LeetCode - 332. Reconstruct Itinerary [Python]

https://leetcode.com/problems/reconstruct-itinerary Reconstruct Itinerary - LeetCode Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepa..

greentea31.tistory.com

LeetCode의 332번 문제와 아주 유사한 문제이고 풀이도 유사하게 하면 된다.

 


소스 코드

import collections

def solution(tickets):
    answer = []
    ticket_list = collections.defaultdict(list)
    tickets.sort(key=lambda x: x[1], reverse=True)
    
    for depart, arrive in tickets:
        ticket_list[depart].append(arrive)
    
    print(ticket_list)
        
    def dfs(go):
        while ticket_list[go]:
            dfs(ticket_list[go].pop())
        answer.append(go)  # 재귀를 사용해 여행순서가 반대 순서대로 answer에 저장된다.
        
    dfs('ICN')
    return answer[::-1]  # 그래서 반대로 출력해주어야 한다.

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이 과정

words 배열에 있는 단어 중, 현재 단어와 한 글자 차이 나는 단어로 이동이 가능하다고 할 때, begin에서 target까지 가는 가장 짧은 변환 과정을 구하는 문제이다. 변환이 불가능하면 0을 출력하면 된다.

 

이동 가능한 단어간의 거리는 1, 구해야 하는 것은 가장 짧은 경로.... 문제를 읽다보면 BFS가 바로 떠오른다. 이미 방문한 적이 있는 단어는 방문하지 않게끔 처리하면서 BFS로 단어들을 탐색하면서 거리를 측정하면 된다. 각 단어는 노드, 한 글자씩 차이나는 단어 노드들간에 간선이 존재하는 그래프로 생각할 수 있다.

 

어떤 진행 경로에서 방문하지 않았던 단어여도, 다른 진행 경로에서 방문한 단어면 방문 할 필요가 없다. 예를 들어 cat -> cot 경로가 존재하고 cat -> cet 경로에서 다음 단어를 찾는다고 가정해보자. BFS 진행 중, cat -> cet -> cot로 방문이 가능해야 하지만 어차피 이건 cat -> cot 경로보다 정답에 도달하는게 무조건 느리다. 따라서 방문을 할 필요가 없고, 다른 경로에서 이미 방문한 단어이므로 방문 불가능 처리를 해주면 된다. 즉, 경로마다 각자의 진행 경로를 저장할 필요 없이 어떤 경로에서든 이미 방문한 단어 노드이면 방문이 불가능하다고 생각해야 필요없는 경로를 구성하지 않으면서 답을 구할 수 있다.


소스 코드

import collections


discovered = []

def solution(begin, target, words):
    def transferable(a, b):
        same_char = 0
        length = len(a)
        for i in range(length):
            if a[i] == b[i]:
                same_char += 1
        if same_char == length-1:
            return True
        else:
            return False
    
    def bfs(start, words):
        Q = collections.deque()
        Q.append([start, 0])
        discovered = [False for _ in range(len(words))]
        
        while Q:
            cur_word, dist = Q.popleft()
            print(f'cur_word: {cur_word}, dist: {dist}')
            if cur_word == target:
                return dist
            for index, word in enumerate(words):
                if discovered[index]:
                    continue
                if not transferable(cur_word, word):
                    continue
                discovered[index] = True
                Q.append([word, dist+1])
        
        return 0
        
    
    if transferable(begin, target):
        return 1
    
    answer = bfs(begin, words)
    
    return answer

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이 과정

상하좌우로 이동 가능한 좌표계에서, 통과 가능한 공간과 통과 불가능한 벽이 주어질 때, 시작점 (1, 1)에서 도착점 (n, m)까지 경로의 최단 거리를 구하는 문제이다.

 

문제의 예시에서 보이듯이, 위처럼 지도가 주어지면 최단 경로의 길이는 11이 된다.

 

좌표간의 가중치가 존재하지 않으므로 그냥 BFS를 돌려서 최단거리를 구하면 된다.

 

파이썬에서 BFS를 구현 시, 이미 접근한 좌표를 discovered 배열에 넣는, 예를 들어 discovered = [] 상태에서 [1, 1] 방문시 discovered.append([1, 1])로 좌표를 넣어주고 BFS로 진행할 좌표를 찾을 때 not in discovered 조건으로 구현하는 경우가 있다. 그러나 이 문제에서는 효율성 테스트에서 시간 초과가 발생할 것이다. set이나 dictionary 자료형이 아니면 in이나 not in으로 자료형을 탐색하는 연산의 시간복잡도는 O(N)이 된다.

 

이 문제 같은 경우에는 이미 방문한 점을 map에서 2로 바꿔준다거나 하는 방법으로 해결이 가능하다. 별도의 discovered 리스트로 방문 점을 판별하고 싶다면 discovered = [[0 for _ in range(m)] for _ in range(n)] 과 같이 별도의 방문점을 판별하는 2차원 배열을 선언후 값을 바꿔주어야 방문 가능 판단 연산이 O(1)이 된다.


소스 코드

import collections

def solution(maps):
    def bfs(maps, n, m):
        Q = collections.deque()
        Q.append([0, 0, 1])
        dx = [1, 0, -1, 0]
        dy = [0, 1, 0, -1]
        
        while Q:
            cur_y, cur_x, dist = Q.popleft()
            for i in range(4):
                ny = cur_y + dy[i]
                nx = cur_x + dx[i]
                ndist = dist + 1
                if 0 <= ny < n and 0 <= nx < m and maps[ny][nx] == 1:
                    Q.append([ny, nx, ndist])
                    maps[ny][nx] = 2
                    if ny == n-1 and nx == m-1:
                        return ndist
        
        return -1
                
            
    answer = bfs(maps, len(maps), len(maps[0]))
    return answer

# discovered = [] 와 in으로 방문 여부 판단하면 in이 O(N)이라서 시간초과가 발생한다.

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


풀이 과정

A로만 이루어져 있는 글자가 주어질 때, 원하는 문자열로 바꾸려면 조이스틱을 몇 번 조작해야 하는지 구하는 문제이다.

 

위의 예시를 읽으면서 생각해보면 조이스틱을 위나 아래로 몇 번 조작해야 하는지는 구하기가 간단할 것 같다. A는 0, B는 1, C는 2, ... M은 12, N은 다시 12, O는 11 ... Z는 1번 조이스틱을 조작해서 만들 수 있다.

 

문자열의 문자를 읽으면서 각 문자에 대응하는 숫자를 조이스틱의 이동 횟수에 더해주면 위, 아래 조작수는 전부 답에 더해진다. 하지만 왼쪽, 오른쪽 조작은 몇 번 해야할까? 문자열의 길이만큼 조작한다고 하기엔 BBAAA는 좌우 이동이 오른쪽으로 한번만 필요한 문자이다.

 

펜대를 굴려가며 여러 예시를 쓰면서 생각해보면 결국 가장 긴 연속된 A 문자열을 지나가지 않도록 좌우 이동을 해야 최소 이동횟수가 나오겠다는 생각이 든다. 그러면 가능한 최소 경우는 아래와 같다.

 

1. 그냥 문자열의 길이 - 1 만큼 좌우 이동을 해야하는 경우

ex) BABAB -> 모든 B를 A로 바꿔주려면 결국 4번 이동해야 한다. 다른 방법은 존재하지 않는다.

 

2. 왼쪽으로 진행하다 꺾어서 오른쪽으로 진행

ex) ABBBBAAAB -> 왼쪽으로 한번 진행후 B를 A로 바꿔준 뒤, 오른쪽으로 가면서 모든 B를 A로 바꿔주는게 최소 경우이다. 좌우 이동횟수 6번이 최소 횟수이다. 문자열의 길이 - 1 = 8보다 적게 이동이 가능함을 알 수 있다. 가장 긴 연속된 A 문자열 AAA 안쪽은 굳이 지나갈 필요가 없기 때문이다.

 

3. 오른쪽으로 진행하다 왼쪽으로 꺾어서 진행

2번과 비슷한 예시를 생각해볼 수 있다.

 

 


소스 코드

def solution(name):
    answer = 0
    minimum_move = len(name) - 1
    
    for i, char in enumerate(name):
    	# 상하 최소 조작 횟수를 구하는 부분
        answer += min(ord(char) - ord('A'), ord('Z') - ord(char) + 1)
        nxt = i + 1
        
        while nxt < len(name) and name[nxt] == 'A':
            nxt += 1
            
        # 좌우 최소 이동횟수를 구하는 부분          
        minimum_move = min([minimum_move, 2 *i + len(name) - nxt, i + 2 * (len(name) - nxt)])
        
    answer += minimum_move
    return answer

 

+ Recent posts