[백준] 스택수열

백준 1874

스택

그리디

백준

easy

문제

1부터 n까지의 수를 스택에 넣었다가 뽑아 늘어놓음으로써, 하나의 수열을 만들 수 있다. 이때, 스택에 push하는 순서는 반드시 오름차순을 지키도록 한다고 하자. 임의의 수열이 주어졌을 때 스택을 이용해 그 수열을 만들 수 있는지 없는지, 있다면 어떤 순서로 push와 pop 연산을 수행해야 하는지를 알아낼 수 있다. 이를 계산하는 프로그램을 작성하라.

입력

첫 줄에 n (1 ≤ n ≤ 100,000)이 주어진다. 둘째 줄부터 n개의 줄에는 수열을 이루는 1이상 n이하의 정수가 하나씩 순서대로 주어진다. 물론 같은 정수가 두 번 나오는 일은 없다.

8
4
3
6
8
7
5
2
1

출력

입력된 수열을 만들기 위해 필요한 연산을 한 줄에 한 개씩 출력한다. push연산은 +로, pop 연산은 -로 표현하도록 한다. 불가능한 경우 NO를 출력한다.

+
+
+
+
-
-
+
+
-
+
+
-
-
-
-
-

첫번째 접근법

Sorting 후, 재귀 호출

일단, 문제가 이해가 안갔다. 스택을 이용해서, 입력받은 순서대로 수열을 스택에 쌓인 값으로 만들 수 있냐는 문제인데 스택에 push 하는 것은 오름차순을 지켜야 한단다. 뭔말인가 했다. :sweat_smile:

4 3 6 8 7 5 2 1

위와 같이 값이 주어지면, 스택에는 오름차순으로만 넣을 수 있고, 이 스택을 push, pop 하는 과정을 통해 위의 수열을 똑같이 만들 수 있냐는 문제이다.

문제를 이해하고 아래와 같이 풀면 된다고 생각했다.

  • 입력받은 숫자를 오름차순 sorting
  • sorting 된 리스트를 순서대로 push
  • push 할 때 마다, pop할 수 있는 수 인지 확인
    • YES : 1) pop 2) find[] = True
      • check_seq(indx + 1) 재귀 호출 (남아 있는 스택에서 pop 할 수 있는 값이 있는지 확인)
    • NO : 아무것도 하지 않고 return index
  • 모든 배열 list를 다 순환했을 때 check한 index값이 수열의 size와 동일하다면, 수열을 모두 pop한 뜻이므로 YES
  • 아니라면 NO 출력
# 파이썬 재귀 최대 1000
import sys
sys.setrecursionlimit(100000)

n = int(input())

find = [False for i in range(200000)]
stack = list()
idx = 0
seq = list()

answer = list()

def solve():
    for i in range(n):
        seq.append(int(input()))

    global idx

    data = seq[:]  # 복사
    data.sort()
    for i in range(n):
        if not find[data[i]]:
            stack.append(data[i])
            answer.append("+")
            idx = check_seq(idx)

    if n == idx:
        for i in range(len(answer)):
            print(answer[i])
    else:
        print("NO")

def check_seq(index):
    if len(stack) < 1:
        return index

    chk_value = stack[-1]
    if chk_value == seq[index]:
        pop_value = stack.pop()
        answer.append("-")
        find[pop_value] = True
        return check_seq(index + 1)

    return index

What was the problem?

  1. 런타임 에러 파이썬에서는 1,000개 이상의 재귀는 기본적으로 제한하고 있다.

    python has a recurison depth of < 1000~. so for the purpose of this assignment I'm increasing it

    따라서, 아래 코드를 최상단에 추가해줘야 한다.

     import sys
     sys.setrecursionlimit(100000)
    
  2. 경우의 수 배열 size 초과

    입력 제한은 1 ≤ n ≤ 100,000 이라 처음에 find[] 배열을 find = [False for i in range(100000)] 0부터 10,000 공간을 만들었다. But n은 1부터 10,000까지 입력되는 것이므로 나의 배열은 999999 까지 밖에 못받아서 런타임에러가 났던 것이다. find = [False for i in range(1, 100001)] 이렇게 바꿔줘야 한다.

    런타임 에러가 날 경우
    입력제한에 따른 배열 size를 잘 살펴보자
    최소, 최대 입력을 잘 살펴보자.

강의 해법

Greedy Algorithm

강의해법은 입력 받을 때마다 바로 최적의 해를 찾는 Greedy 기법을 사용했다.

사실, 입력받은 숫자 기준으로 하위 숫자를 바로 Stack에 쌓는 아이디어는 정말 떠오르지 않았다.

  • 입력받을 때마다 해당 숫자 아래 숫자를 모두 스택에 쌓는다 (이렇게하면 무조건 오름차순으로 쌓아지니까)
  • pop했는데, 값이 지금 데이터가 아니면 불가능한 경우이므로 종료한다.
count = 1
stack = []
result = []

for i in range(1, n + 1): # 데이터 개수만큼 반복
    data = int(input()) # 입력받을 때마다, 체크해준다.
    while count <= data: # 입력 받은 데이터에 도달할 때까지 삽입
        stack.append(count)
        count += 1
        result.append("+")
    if stack[-1] == data: # 스택의 최상위 원소가 데이터와 같을 때 출력
        stack.pop()
        result.append("-")
    else: # 불가능한 경우
        print("NO")
        exit(0)

print("\n".join(result))

Conclusion

강의해법과 내 해법을 비교해봤다.

구분 메모리 시간
내 해법 51416KB 4212ms
강의 해법 31712KB 4688ms
  • 재귀를 사용했으므로, 메모리가 내 해법이 더 크게 나왔다.
  • 하지만 시간적 측면에선 아주 근소하게 더 좋은(?) 것 같다.
  • 사실 나의 해법은 sorting 까지 들어갔고, 수열을 만들 수 없는 NO case일 경우에도 모든 입력 숫자를 for 반복이 끝나야지 이게 NO인지 알 수 있는 반면에 강의해법은 입력받을 때마다 체크를 해서, pop 했을 때 자신의 숫자가 아니라면 바로 exit(0) 을 하므로 더 효율적일 것 이라고 생각이 든다.