코딩테스트

[Python] 프로그래머스 / Level3 / 표 편집 (Linked List)

크스디 2021. 8. 31. 15:50

문제 : https://programmers.co.kr/learn/courses/30/lessons/81303

 

코딩테스트 연습 - 표 편집

8 2 ["D 2","C","U 3","C","D 4","C","U 2","Z","Z"] "OOOOXOOO" 8 2 ["D 2","C","U 3","C","D 4","C","U 2","Z","Z","U 1","C"] "OOXOXOOO"

programmers.co.kr

 

2021 카카오 채용연계형 인턴십 3번으로 출제된 문제이다.

문제 이해 자체는 어렵지 않지만, 효율성 테스트를 통과하는 것이 어려워 난이도가 높다.

 

대놓고 첫째줄에 경고 문구가 적혀있다,, 😨

[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]

 

효율성 테스트를 통과하기 위해서 가장 중요한 것은 문제의 제한사항을 꼼꼼히 파악하는 것이다.

 

 

🤔 제한사항 분석

명령들이 담겨있는 리스트를 cmds로 이름짓고, 각 명령을 cmd라고 할 수 있다.

for cmd in cmds : ~ 로 풀이를 시작해야 한다는 것은 명백하다. 조건에 따르면 이는 O(10^4) time이 소요된다.

 

조건에서, n의 크기는 O(10^6)이기 때문에 cmd가 돌아가는 for 문 안에서 O(n)에 관한 동작을 다룬다면 ?

O(10^4) * O(10^6) = O(10^10) 으로 효율성 테스트를 절대 통과할 수 없게 된다..

 

for문 안에서는 O(log n)이나 O(1) 이 소요되는 동작이 반복되어야 한다는 것이다.

파이썬의 일반적인 자료구조 List로 solution 함수를 작성했다고 가정해보자.

 

List에서의 삭제 연산은 O(N) time이 소요된다. 중간에서 원소를 삭제했다고 가정했을 시, 그 뒤의 원소들을 한 칸씩 앞으로 끌어와야 하기 때문이다. 이 문제에서는 삭제 연산이 상당히 많이 일어날 수 있기 때문에 List는 풀이에 적절하지 않다는 것을 알 수 있다.

 

그래서 이 문제에서 사용하기 적절한 자료구조는 Linked List 이다.

Linked List는 insertion과 deletion 연산에서 O(1)의 효율성을 가지는 자료구조이기 때문이다.

next와 prev를 모두 다뤄야해서 정확히는 Double Linked List를 선택해야한다 !

 

🙄 여기서 중요한 의문이 생긴다. 

이 문제에서는 삭제 뿐만 아니라 탐색도 고려해야 하는데 ?? Up / Down 에서 탐색이 일어나고, 링크드 리스트에서의 탐색은 O(N) time이 소요되는데 ??

 

마지막 제한 사항이 이를 해소해준다. for cmd in cmds 는 최대 200,000번 반복된다. 여기서 계속 Up Down만 일어난다고 해도, 모든 X의 값을 합한 최대 값이 1,000,000이기 때문에 평균적인 X값은 5정도가 된다고 이해할 수 있다. 만약 극단적인 상황을 가정해 500,000이 연속 두 번 나온다 해도 효율성 테스트는 통과할 수 있을 것이다.

 

 

😊 소스 코드

파이썬에서 어떻게 링크드 리스트 코드를 작성해야할까 고민하다가, Dictionary 자료구조를 활용해보았다.

{ nodeNum : [prev, next] } 와 같은 구조로 설계하였고, 맨 HEAD는 { nodeNum : ['HEAD', next] }로 설정하였다.

TAIL도 같은 방식으로 설정해주었다.

 

def solution(n, k, cmds):
    answer = ['O'] * n
    # Double Linked List
    DLL = {i : [i-1,i+1] for i in range(n)}
    DLL[0][0] = 'HEAD'; DLL[n-1][1] = 'TAIL'
    stack = []
    
    for cmd in cmds:
        if cmd[0] == 'D':
            x = int(cmd[2:])
            for _ in range(x):
                k = DLL[k][1]
                
        elif cmd[0] == 'U':
            x = int(cmd[2:])
            for _ in range(x):
                k = DLL[k][0]
        
        elif cmd[0] == 'C':
            prev, next = DLL[k]
            answer[k] = 'X'

            if prev == 'HEAD':
                stack.append([k,prev,next])
                DLL[next][0] = 'HEAD'
                k = next
            
            elif next == 'TAIL':
                stack.append([k,prev,next]) # 삭제
                DLL[prev][1] = 'TAIL'
                k = prev    # 윗 행 선택
            
            else:
                stack.append([k,prev,next]) # 삭제
                DLL[prev][1] = next
                DLL[next][0] = prev
                k = next   # 아래 행 선택
                
        elif cmd[0] == 'Z':
            now, prev, next = stack.pop()
            answer[now] = 'O'
            
            if prev == 'HEAD':
                DLL[next][0] = now
            elif next == 'TAIL':
                DLL[prev][1] = now
            else:
                DLL[prev][1] = now
                DLL[next][0] = now          
    
    return ''.join(answer)