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