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

 

11724번: 연결 요소의 개수

첫째 줄에 정점의 개수 N과 간선의 개수 M이 주어진다. (1 ≤ N ≤ 1,000, 0 ≤ M ≤ N×(N-1)/2) 둘째 줄부터 M개의 줄에 간선의 양 끝점 u와 v가 주어진다. (1 ≤ u, v ≤ N, u ≠ v) 같은 간선은 한 번만 주

www.acmicpc.net

 

그래프 내 연결되어 있는 그룹이 몇개인지 묻는 문제다. BFS든 DFS든 돌리면서 방문 정보를 저장해주는 visited 배열에서 False인 요소가 존재하면 그룹 수 +1 해주고 다시 BFS/DFS를 돌리면 되는 문제다. visited 배열의 모든 값이 True가 되면 총 그룹 수를 출력해주면 된다.

 

나는 BFS로 접근해서 풀었다. 이런식으로 그래프가 끊어져 있을 때 어떻게 풀면 될지 고민헀는데 이 문제같은 경우에는 노드 번호가 1부터 N까지 정해져 있어서 수월했다. 

 

코드는 다음과 같다.

import sys

input = sys.stdin.readline

from collections import deque


def bfs(start):
    visited[start] = True
    queue = deque()
    queue.append(start)

    while queue:
        x = queue.popleft()
        for y in graph[x]:
            if not visited[y]:
                visited[y] = True
                queue.append(y)


N, M = map(int, input().split(" "))
graph = [[] for _ in range(N + 1)]
visited = [False] * (N + 1)
for _ in range(M):
    u, v = map(int, input().split(" "))
    graph[u].append(v)
    graph[v].append(u)

cnt = 0
for i in range(1, N + 1):
    if not visited[i]:
        cnt += 1
        bfs(i)

print(cnt)

 

보이는가 저 2년전 ...

 


2년동안 발전은 했는데 .. 2년 내내 했으면 눈부신 발전 덕분에 취업을 이미 하지 않았을까 하는 생각이 눈물이 앞을 가리는 문제였다.

 

1914번: 하노이 탑

세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승들이 다음 규칙에 따라 첫 번째 장대에서 세 번째 장대로

www.acmicpc.net

 

이 난이도가 실버 2? 거짓말이라고 해달라....

재귀함수는 문제를 이해하고 점화식 도출해서 코드로 구현하는 그 일련의 과정이 감이 안오면 정말 손도 못 대겠더라. 끙끙 고민하다가 결국 다른 사람의 풀이를 보고 이해했다.

 

영차영차

 

문제를 살펴보면 원반 N개를 해결하려면 원반 N-1개인 상황을 해결해야 한다. 즉, 다음의 과정을 도출해낼 수 있다.

  1. N개의 원반이 주어지면 위에서부터 N-1개의 원반을 두번째 기둥으로 옮김
    현재 상태 -> 기둥 1에는 제일 큰 N번째 원반이 남아있으며 기둥 2에는 N-1개의 원반이 있다.
  2. 기둥 1의 N번째 원반을 목표 지점인 기둥 3로 옮긴다
  3. 기둥 2에 남아있는 N-1개의 원반을 목표 지점인 기둥 3로 옮기면 된다.

 

문제에서 20이 넘어가면 횟수만 출력하라 했는데, 재귀의 깊이가 너무 깊어질 수 있어 20이 넘어가면 단순히 값만 출력하라고 하는 듯 하다. 원판이 n개 일 때, 2^n -1(=2의 n승 빼기 1)번의 이동으로 원판을 모두 옮길 수 있다. 

 

이를 코드로 구현하면 다음과 같다.

import sys

input = sys.stdin.readline


# start -> end로 원반 disc개 이동
def hanoi(start, mid, end, disc):
    if disc == 1:  #
        print(start, end)  # 출력
    else:
        hanoi(start, end, mid, disc - 1)  # 기둥1 -> 기둥2 (n-1개를 잠시 이동)
        hanoi(start, mid, end, 1)  # 가장 큰 원반 기둥1 -> 기둥 3
        hanoi(mid, start, end, disc - 1)  # 기둥2 -> 기둥3


N = int(input())

print(2 ** N - 1)
if N <= 20:
    hanoi(1, 2, 3, N)


재귀... 재귀.... 이름도 귀신같아가지고 아주 무서운 놈이다 ... 

(티스토리야 반갑다... 카카오 서버가 터지는 사태 이후로 티스토리 접속이 원활하지 않아서 지난주 금요일 이후로 오랜만에 접속해서 TIL을 올리는 것 같다!)

 

이 문제는 사실 꽤 정답까지 근접했던 것 같은데 마지막 한방이 부족했다. 순간순간에 어떤 자료구조를 쓰는게 효율적인지 아직 손에 익지 않은 탓이겠지.

 

문제에서 원하는 좌표 압축이란 i번째 수보다 작은 서로 다른 좌표의 개수를 Xi`에 저장하는 것이다. 즉 중복을 허용하지 않는 본인보다 작은 요소의 개수를 세어주면 된다.

 

  • 중복 허용 X -> set() 으로 변환하여 중복 제거
  • 본인보다 작은 요소의 개수 : sorted()하여 정렬한다. 이때 인덱스값이 해당 요소보다 작은 값의 개수이다.

 

여기까지는 어렵지 않게 떠올렸다. 다만 인덱스값과 해당 데이터를 매번 입력받은 data를 돌면서 index() 함수로 찾았다는게 문제였다..^^ 이럴 필요가 전혀 하등! 없다. 그러나 머리가 왜인지 안 돌아가서... 살짝 구글링했다. 아쉽다... 

 

dictionary 자료구조를 사용하여 (key:value) - (값 : 인덱스) 형태로 저장해준 뒤 data를 돌면서 해당 자료를 dictionary에서 찾으면 된다. 그래야 정답을 출력할 때 시간초과가 안 난다. 

 

코드는 다음과 같다. 마지막은 띄어쓰기 때문에 한칸 빼고 출력해줬다.

import sys

input = sys.stdin.readline

N = int(input())

data = list(map(int, input().split(" ")))

sorted_data = sorted((set(data)))

dictionary = {sorted_data[i]: i for i in range(len(sorted_data))}

answer = ""
for d in data:
    answer += str(dictionary[d]) + " "

print(answer[:-1])

index()함수 썼다가 dictionary로 트니까 바로 정답..!

 

 


그 순간에 적합한 자료구조와 로직을 떠올리는 건 연습으로 얼마든지 해낼 수 있다고 믿는다. 꾸준히 꾸준히!

'Algorithm > BOJ' 카테고리의 다른 글

[백준] 11724 연결요소의 개수 - python  (0) 2022.11.03
[백준] 1914 하노이 탑 - python  (0) 2022.10.18
[백준] 2493 탑 - python  (0) 2022.10.14
[백준] 10799 쇠막대기 - python  (0) 2022.10.13
[백준] 1874 스택 수열 - python  (0) 2022.10.13
 

2493번: 탑

첫째 줄에 탑의 수를 나타내는 정수 N이 주어진다. N은 1 이상 500,000 이하이다. 둘째 줄에는 N개의 탑들의 높이가 직선상에 놓인 순서대로 하나의 빈칸을 사이에 두고 주어진다. 탑들의 높이는 1

www.acmicpc.net

 

문제를 이해하면 다음과 같다.

높이가 다른 탑이 일렬로 있고, 모든 탑은 자기 기준 왼쪽으로 레이저를 송신한다. 수신 가능한 탑은 해당 레이저가 발사된 탑보다 높이가 크거나 같아야 하며, 레이저는 가장 먼저 닿은 탑 1개에서만 수신된다.

 

처음에 한 1시간정도 계속 뒤에서 접근해가면서 봐서 끙끙 앓았다. 앞에서 어느 탑이 수신 가능한지 알 수 없는데 뒤에서부터 본 게 잘못이었다.

 

자고 일어나서 다음날 다시 앞에서부터 보는 방향으로 생각의 방향을 틀었다. 앞에서부터 차근차근 보면 된다. 수신 가능한 탑을 배열로 저장하고, 최신순으로 꺼내보면 된다. 

 

  1. 첫번째 탑 : 이전 탑이 없으므로 수신 가능한 탑 배열에 (높이, 탑번호) 넣어주기만 하면 된다.
  2. 두번째 탑 : 수신 가능한 탑 배열에서 pop() 하여 높이를 비교한다. pop() 한 요소의 데이터는 prev_를 붙여 저장했고 현재 탑의 데이터는 now_를 붙여 저장했다.
    • prev_h > now_h : prev_i번째 탑이 현재 레이저를 수신 가능한 경우.
      pop()한 데이터를 다시 수신가능한 탑에 append해줘야 한다. 현재 탑 데이터도 수신 가능한 탑 배열에 append
    • prev_h == now_h : prev_i번째 탑이 현재 레이저를 수신 가능한 경우.
      높이가 같은 경우 최신 탑이 먼저 수신하게 되므로 현재 탑의 데이터만 수신 가능한 탑 배열에 append
    • prev_h < now_h : prev_i번째 탑이 현재 레이저를 수신 불가능한 경우.
      pop()한 데이터를 저장해줄 필요 없이 넘어간다.
      만약 모든 데이터를 다 훑었음에도 수신 가능한 탑이 없다면 현재 탑의 데이터를 수신 가능한 탑 배열에 append해주고 넘어간다.
  3. i번쨰 탑 : 뒤와 동일한 로직이다.

이렇게 풀어주니 별 문제 없이 통과했다!

코드는 다음과 같다.

import sys

input = sys.stdin.readline

N = int(input())
Heights = list(map(int, input().split(" ")))

# 맨 앞 탑은 수신 불가능하므로 미리 저장하고 고려X
answer = [0]
available_tower = [(Heights[0], 1)]  # (높이, 탑번호)

for i in range(1, len(Heights)):
    now_h, now_i = Heights[i], i + 1

    received = False
    while len(available_tower) > 0:
        prev_h, prev_i = available_tower.pop()
        # 수신 가능한 탑 발견
        if prev_h > now_h:
            available_tower.append((prev_h, prev_i))
            answer.append(prev_i)
            available_tower.append((now_h, now_i))
            received = True
            break
        # 수신 가능한 탑 발견
        # 높이가 같은 경우 최신 탑이 더 우선이므로 최신 탑 데이터만 저장
        elif prev_h == now_h:
            answer.append(prev_i)
            available_tower.append((now_h, now_i))
            received = True
            break
        else:
            continue
    # 모든 탑이 다 수신 불가능한 경우
    if received == False:
        available_tower.append((now_h, now_i))
        answer.append(0)

print(" ".join(map(str, answer)))

 

2번 시도만에 통과했다.

 


현재 접근한 방법으로 문제가 안 풀릴때 빠르게 다른 방향으로 생각의 전환을 해보는 것이 중요하다고 어제 깨달았고, 오늘 직접 전환해보니 실제로 문제가 풀려서 매우 좋다. 골드 V 문제도 풀리기 시작하는 걸 보니 늘긴 느나보다.

 

10799번: 쇠막대기

여러 개의 쇠막대기를 레이저로 절단하려고 한다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자른다. 쇠막대기와 레이저

www.acmicpc.net

 

문제를 읽어보면 '()' 괄호 쌍은 무조건 레이저다. 각 쇠막대기의 끝은 '(' 와 ')'로 나타내며, 쇠막대기 끼리는 겹칠 수 있으나 레이저와 각 쇠막대기의 끝은 겹치지 않는다.

 

처음에는 각 쇠막대기별로 몇개의 레이저를 지나가는지 체크해주는 방향으로 접근했다.

  • '(' 하나만 등장하는 경우 stick 배열에 0을 넣는다.
  • '(', ')'이 연속해서 등장하는 경우 stick 배열 안의 요소를 +1 해주기.
  • ')'이 등장하는 경우 stick 배열에서 pop() 해준다. 

첫번째 시도 코드는 다음과 같다. 사실 작성하면서 stick 배열을 계속 반복하는데 시간초과나는 거 아니야? 싶었다.

import sys

input = sys.stdin.readline

data = input().replace("\n", "")

stick = []
razor = []

answer = 0
i = 0
while i < len(data):
    if data[i] == "(":
        if i != len(data) - 1 and data[i + 1] != ")":
            stick.append(0)
        elif i != len(data) - 1 and data[i + 1] == ")":
            stick = [i + 1 for i in stick]  # 레이저 수 count
            i += 2
            continue
    else:
        answer += stick.pop() + 1  # 레이저 수 + 1 = 조막 수
    i += 1

print(answer)

...^^

슬픈 예감은 틀리지 않지... 한 67%에서 시간초과가 났다. 

 

그래서 접근 방향을 바꿨다. for 반복문을 쓰지 않는 식으로 해주려면 이제 레이저의 관점에서 접근하면 될 것 같았다. 각각의 레이저가 그 순간 몇개의 쇠막대기를 지나가는지 세어주어도 똑같이 풀릴 것 같았다.

  • '(' 하나만 등장하는 경우 현재 유효한 cutting_stick 값을 +1 해준다.
  • '(', ')'이 연속해서 등장하는 경우 razor 배열 안에 cutting_stick을 append() 해주었다. 즉 razor[0]의 값은 0번째 레이저가 몇개의 쇠막대기를 지나가는지 저장하고 있다.
  • ')'이 등장하는 경우 stick 배열에서 pop() 해준다.

최종 답안은 razor 배열 요소의 합과 총 stick의 개수를 더한 값이다. 따라서 쇠막대기가 등장할 때마다 total_stick이라는 이름의 변수로 매번 카운트해줬다.

 

최종 코드는 다음과 같다.

import sys

input = sys.stdin.readline

data = input().replace("\n", "")

cutting_stick = 0
total_stick = 0
razor = []

i = 0
while i < len(data):
    if data[i] == "(":
        # 쇠막대기 등장
        if i != len(data) - 1 and data[i + 1] != ")":
            cutting_stick += 1
            total_stick += 1
        # 레이저 등장
        elif i != len(data) - 1 and data[i + 1] == ")":
            razor.append(cutting_stick) # 몇개의 막대기를 지나가는지 count
            i += 2
            continue
    # 쇠막대기 끝
    else:
        cutting_stick -= 1
    i += 1

print(sum(razor) + total_stick)

제일 짜릿한 화면

 


 

모든 문제가 이렇게 아이디어가 바로 잡혔으면 좋겠다. 매일 정진하자!

 

1874번: 스택 수열

1부터 n까지에 수에 대해 차례로 [push, push, push, push, pop, pop, push, push, pop, push, push, pop, pop, pop, pop, pop] 연산을 수행하면 수열 [4, 3, 6, 8, 7, 5, 2, 1]을 얻을 수 있다.

www.acmicpc.net

 

문제에서 시키는대로 단순 구현만 하면 되는 문제다. 문제 이해를 잘못해서 2시간이나 걸렸다... 끙 아이디어는 맞게 잡은 것 같은데 자꾸 코드가 불필요하게 지저분해져서 잠깐 방향이 맞나 타 코드를 참고했고 너무 꼬아서 생각했다는 걸 깨달았다. 문제를 너무 어렵게 보지 말고, 매번 조건을 달아서 처리해줘야 하는 경우에는 필시... 내가 생각못한 테케에서 걸리는 경우가 많다. 범용적이게 코드를 짜자.

 

스택에 넣을 수 있는 숫자는 오름차순만 가능하다. 즉 내가 지금까지 넣은 숫자의 개수를 갖고 있으면 내가 새로운 숫자를 어디부터 시작해야 되는지 알 수 있다.

 

내가 넣은 숫자의 개수보다 출력해야 하는 숫자가 크면 더 넣어준다. 다 넣어준 뒤, pop()을 하여 해당 숫자가 출력해야하는 숫자인지 체크한다. 만약 pop() 했을 때 해당 숫자가 출력해야 하는 숫자와 다르다면 스택 수열을 만들 수 없는 상황이므로 바로 반복문을 종료하면 된다. 나는 push/pop 연산을 담은 answer 배열을 초기화해주고 끝냈다.

 

코드는 다음과 같다. (깃헙에는 삽질했던 버전을 함께 올려두었다... )

import sys

input = sys.stdin.readline

N = int(input())
DATA = [int(input()) for _ in range(N)]

count = 0
answer = []
nums = []
for num in DATA:
    while count < num:
        answer.append("+")
        count += 1
        nums.append(count)
    top = nums.pop()
    if top == num:
        answer.append("-")
    else:
        answer.clear()
        break

if len(answer) == 0:
    print("NO")
else:
    for i in answer:
        print(i)

 

30분에 한번씩 시도했다고 치면 딱 2시간정도 걸린 문제... ㅎ

 


어려운 문제가 아니어도 말리기 시작하면 진짜 끝도 없이 말리는 것 같다. 문제를 풀다가 딱 생각을 환기하고 새로운 마음으로 풀 수 있게 노력해야지.

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

 

1406번: 에디터

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

www.acmicpc.net

 

처음에 보고 단순 구현 문제라고 생각했다. 물론 그렇게 단순하게 생각해서 문자열 슬라이싱했다가 처참하게 시간초과가 났다. 당연하긴 하다. B나 P 입력이 들어오면 문자열 cursor번째까지 살펴보고, 또 그 뒤도 살펴보는 식으로 문자열의 길이만큼을 계속 살펴보게 되어 시간효율성이 떨어진다. 

 

따라서 커서가 움직일 때마다 해당 데이터를 뽑아서 갖고 있는 리스트(pop_data)를 추가하는 방향으로 코드를 수정했다. 커서를 기준으로 왼쪽 데이터가 data 리스트에, 오른쪽 데이터가 pop_data에 저장되는 식이다! 그러면 매번 O(1)의 시간복잡도만 발생하여 최대 명령어의 개수(M)까지만 시간이 소요되어 0.3초라는 시간제한을 통과할 수 있다.

  • L : 커서 왼쪽 이동. data의 길이가 0이 아닐 때, data.pop() -> pop_data에 append()
  • D : 커서 오른쪽 이동. pop_data의 길이가 0이 아닐 때, pop_data.pop() -> data에 append()
  • B : 커서 기준 왼쪽의 글자 제거. data의 길이가 0이 아닐 때, data.pop()
  • P $ : 커서 기준 왼쪽에 글자 삽입. data에 바로 글자 append()

 

리스트를 커서를 기준으로 2개 둔다는 아이디어만 떠올리면 금방 구현할 수 있는 문제다. 마지막에 출력해줄 때는 data안의 요소와 pop_data안의 요소는 역순으로 출력해주면 된다. (리스트의 역순 접근은 [::-1] 이다. 잊지말자!)

 

코드는 다음과 같다.

import sys

input = sys.stdin.readline

data = list(map(str, input().replace("\n", "")))
m = int(input())
commands = [input() for _ in range(m)]

pop_data = []
for c in commands:
    if c[0] == "L":
        if len(data) != 0:
            pop_data.append(data.pop())
        else:
            continue
    elif c[0] == "D":
        if len(pop_data) != 0:
            data.append(pop_data.pop())
        else:
            continue
    elif c[0] == "B":
        if len(data) != 0:
            data.pop()
        else:
            continue
    elif c[0] == "P":
        input_c = list(c.split(" "))[1][0]
        data.append(input_c)


print("".join(map(str, data)) + "".join(map(str, pop_data[::-1])))

 

리스트 2개 써야 시간 초과가 안난다 :)

 

스택을 언제 쓰는게 효율적인지 살짝씩 감을 잡아가는 것 같다. 더 풀어보자!

 

1935번: 후위 표기식2

첫째 줄에 피연산자의 개수(1 ≤ N ≤ 26) 가 주어진다. 그리고 둘째 줄에는 후위 표기식이 주어진다. (여기서 피연산자는 A~Z의 영대문자이며, A부터 순서대로 N개의 영대문자만이 사용되며, 길이

www.acmicpc.net

 

후위 표기식은 연산자가 피연산자의 뒤에 등장하는 구조다. 앞에서부터 차근차근 보면서 연산자가 등장한 경우 그간의 숫자 중 가장 마지막 2개를 뽑아와 계산하면 되는 문제였다.

 

문제 이해를 이상하게 해서 1시간 넘게 헤맸다. 방향을 잘못 잡으니까 계속 그 방향으로만 생각이 빙빙 돌아서 결국 2시간정도 지난 뒤에 다른 사람 풀이를 봤더니 슬퍼졌다. 도대체 왜 난 뒤에서부터 계산한거지 ...? 

 

코드는 다음과 같다.

import sys

input = sys.stdin.readline

n = int(input())
data = input()
data = data.replace("\n", "")
nums = [int(input()) for _ in range(n)]

# nums인덱스 <- ord() - 65

tmp = []

num_list = []
for s in data:
    # 숫자
    if 65 <= ord(s) <= 90:
        num_list.append(nums[ord(s) - ord("A")])
    # 연산자
    else:
        back = num_list.pop()
        front = num_list.pop()
        if s == "+":
            num_list.append(front + back)
        elif s == "-":
            num_list.append(front - back)
        elif s == "*":
            num_list.append(front * back)
        else:
            num_list.append(front / back)


# 소숫점 두번째 자리까지 표기
print("{:.2f}".format(num_list[0]))

 


다시금 기억하면 좋을 파이썬 문법 기록하기

- ord('문자') : 아스키코드값으로 바꿔준다

- "{:.2f}".format(숫자) : 소숫점 n번째 자리까지 출력

+ Recent posts