[Programmers/ level3] ν‘œ νŽΈμ§‘πŸ™„

5 λΆ„ μ†Œμš”


πŸ‘©λ¬Έμ œ

[λ³Έ λ¬Έμ œλŠ” μ •ν™•μ„±κ³Ό νš¨μœ¨μ„± ν…ŒμŠ€νŠΈ 각각 μ μˆ˜κ°€ μžˆλŠ” λ¬Έμ œμž…λ‹ˆλ‹€.]

μ—…λ¬΄μš© μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό κ°œλ°œν•˜λŠ” λ‹ˆλ‹ˆμ¦ˆμ›μŠ€μ˜ 인턴인 μ•™λͺ¬λ“œλŠ” λͺ…λ Ήμ–΄ 기반으둜 ν‘œμ˜ 행을 선택, μ‚­μ œ, λ³΅κ΅¬ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•˜λŠ” 과제λ₯Ό λ§‘μ•˜μŠ΅λ‹ˆλ‹€. μ„ΈλΆ€ μš”κ΅¬ 사항은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

ν–‰ 번호 이름
0 무지
1 콘
2 μ–΄ν”ΌμΉ˜
3 μ œμ΄μ§€
4 ν”„λ‘œλ„
5 λ„€μ˜€
6 튜브
7 라이언

μœ„ κ·Έλ¦Όμ—μ„œ νŒŒλž€μƒ‰μœΌλ‘œ 칠해진 칸은 ν˜„μž¬ μ„ νƒλœ 행을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 단, ν•œ λ²ˆμ— ν•œ ν–‰λ§Œ 선택할 수 있으며, ν‘œμ˜ λ²”μœ„(0ν–‰ ~ λ§ˆμ§€λ§‰ ν–‰)λ₯Ό λ²—μ–΄λ‚  수 μ—†μŠ΅λ‹ˆλ‹€. μ΄λ•Œ, λ‹€μŒκ³Ό 같은 λͺ…λ Ήμ–΄λ₯Ό μ΄μš©ν•˜μ—¬ ν‘œλ₯Ό νŽΈμ§‘ν•©λ‹ˆλ‹€.

  • β€œU X”: ν˜„μž¬ μ„ νƒλœ ν–‰μ—μ„œ XμΉΈ μœ„μ— μžˆλŠ” 행을 μ„ νƒν•©λ‹ˆλ‹€.
  • β€œD X”: ν˜„μž¬ μ„ νƒλœ ν–‰μ—μ„œ XμΉΈ μ•„λž˜μ— μžˆλŠ” 행을 μ„ νƒν•©λ‹ˆλ‹€.
  • β€œC” : ν˜„μž¬ μ„ νƒλœ 행을 μ‚­μ œν•œ ν›„, λ°”λ‘œ μ•„λž˜ 행을 μ„ νƒν•©λ‹ˆλ‹€. 단, μ‚­μ œλœ 행이 κ°€μž₯ λ§ˆμ§€λ§‰ 행인 경우 λ°”λ‘œ μœ— 행을 μ„ νƒν•©λ‹ˆλ‹€.
  • β€œZ” : κ°€μž₯ μ΅œκ·Όμ— μ‚­μ œλœ 행을 μ›λž˜λŒ€λ‘œ λ³΅κ΅¬ν•©λ‹ˆλ‹€. 단, ν˜„μž¬ μ„ νƒλœ 행은 λ°”λ€Œμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


πŸ‘©μ œν•œμ‚¬ν•­

  • μ œν•œμ‚¬ν•­
    • 5 ≀ n ≀ 1,000,000
    • 0 ≀ k < n
    • 1 ≀ cmd의 μ›μ†Œ 개수 ≀ 200,000
      • cmd의 각 μ›μ†ŒλŠ” β€œU X”, β€œD X”, β€œC”, β€œZ” 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€.
      • XλŠ” 1 이상 300,000 μ΄ν•˜μΈ μžμ—°μˆ˜μ΄λ©° 0으둜 μ‹œμž‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
      • Xκ°€ λ‚˜νƒ€λ‚΄λŠ” μžμ—°μˆ˜μ— β€˜,’ λŠ” 주어지지 μ•ŠμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ 123,456의 경우 123456으둜 μ£Όμ–΄μ§‘λ‹ˆλ‹€.
      • cmd에 λ“±μž₯ν•˜λŠ” λͺ¨λ“  Xλ“€μ˜ 값을 ν•©μΉœ κ²°κ³Όκ°€ 1,000,000 μ΄ν•˜μΈ 경우만 μž…λ ₯으둜 μ£Όμ–΄μ§‘λ‹ˆλ‹€.
      • ν‘œμ˜ λͺ¨λ“  행을 μ œκ±°ν•˜μ—¬, 행이 ν•˜λ‚˜λ„ 남지 μ•ŠλŠ” κ²½μš°λŠ” μž…λ ₯으둜 주어지지 μ•ŠμŠ΅λ‹ˆλ‹€.
      • λ³Έλ¬Έμ—μ„œ 각 행이 제거되고 λ³΅κ΅¬λ˜λŠ” 과정을 보닀 μžμ—°μŠ€λŸ½κ²Œ 보이기 μœ„ν•΄ β€œμ΄λ¦„β€ 열을 μ‚¬μš©ν•˜μ˜€μœΌλ‚˜, β€œμ΄λ¦„β€μ—΄μ˜ λ‚΄μš©μ΄ μ‹€μ œ 문제λ₯Ό ν‘ΈλŠ” 과정에 ν•„μš”ν•˜μ§€λŠ” μ•ŠμŠ΅λ‹ˆλ‹€. β€œμ΄λ¦„β€μ—΄μ—λŠ” μ„œλ‘œ λ‹€λ₯Έ 이름듀이 쀑볡없이 μ±„μ›Œμ Έ μžˆλ‹€κ³  κ°€μ •ν•˜κ³  문제λ₯Ό ν•΄κ²°ν•΄ μ£Όμ„Έμš”.
    • ν‘œμ˜ λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λŠ” 이동은 μž…λ ₯으둜 주어지지 μ•ŠμŠ΅λ‹ˆλ‹€.
    • μ›λž˜λŒ€λ‘œ 볡ꡬ할 행이 없을 λ•Œ(즉, μ‚­μ œλœ 행이 없을 λ•Œ) β€œZ”가 λͺ…λ Ήμ–΄λ‘œ μ£Όμ–΄μ§€λŠ” κ²½μš°λŠ” μ—†μŠ΅λ‹ˆλ‹€.
    • 정닡은 ν‘œμ˜ 0ν–‰λΆ€ν„° n - 1ν–‰κΉŒμ§€μ— ν•΄λ‹Ήλ˜λŠ” O, Xλ₯Ό μˆœμ„œλŒ€λ‘œ 이어뢙인 λ¬Έμžμ—΄ ν˜•νƒœλ‘œ return ν•΄μ£Όμ„Έμš”.
  • μ •ν™•μ„± ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μ œν•œ 사항
    • 5 ≀ n ≀ 1,000
    • 1 ≀ cmd의 μ›μ†Œ 개수 ≀ 1,000
  • νš¨μœ¨μ„± ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μ œν•œ 사항
    • 주어진 쑰건 μ™Έ μΆ”κ°€ μ œν•œμ‚¬ν•­ μ—†μŠ΅λ‹ˆλ‹€.


πŸ‘©μž…μΆœλ ₯ 예

n k cmd result
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”


πŸ‘©μ œν•œμ‹œκ°„ μ•ˆλ‚΄

  • μ •ν™•μ„± ν…ŒμŠ€νŠΈ: 10초
  • νš¨μœ¨μ„± ν…ŒμŠ€νŠΈ: μ–Έμ–΄λ³„λ‘œ μž‘μ„±λœ μ •λ‹΅ μ½”λ“œμ˜ μ‹€ν–‰ μ‹œκ°„μ˜ 적정 배수


πŸ™‹β€β™€οΈμ ‘κ·Ό 방법

ν•΄λ‹Ή λ¬Έμ œμ—μ„œμ˜ 핡심은 νŠΉμ • μžλ£Œκ΅¬μ‘°μ— λŒ€ν•œ μ‚½μž…, μ‚­μ œ, νƒμƒ‰μ˜ λ°˜λ³΅μ΄λ‹€.

λ”±νžˆ μ–΄λ €μš΄ 뢀뢄은 μ—†μœΌλ‚˜ νš¨μœ¨μ„± ν…ŒμŠ€νŠΈκ°€ μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— 이에 μ μ ˆν•œ 자료ꡬ쑰λ₯Ό 선택해 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€.초기 ν•„μžλŠ” 리슀트λ₯Ό ν†΅ν•΄μ„œ μ‚½μž…, μ‚­μ œ, 검색을 μ§„ν–‰ν•˜μ—¬ λ‹€μŒκ³Ό 같은 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ˜€λ‹€.

πŸ™‹β€β™€οΈμ½”λ“œ: 리슀트

def solution(n, k, cmd):
    answer, lst = [], []
    for i in range(n):
        answer.append("X")
        lst.append(i)
    storage = []
    
    for str in cmd:
        if str == "C":
            storage.append((k, lst[k]))
            del lst[k]
            if k == len(lst):
                k-=1
        elif str == "Z":
            idx, val = storage.pop()
            lst.insert(idx, val)
            if idx <= k:
                k += 1
        else:
            c, a = str.split(" ")
            if c == "U":
                k -= int(a)
            elif c == "D":
                k += int(a)
    for i in lst:
        answer[i] = "O"
    return "".join(answer)

λ¦¬μŠ€νŠΈμ„ μ‚¬μš©ν•  경우, 각 연산에 λŒ€ν•œ μ‹œκ°„λ³΅μž‘λ„λ₯Ό μ‚΄νŽ΄λ³΄μž!

  • C: 인덱슀λ₯Ό μ΄μš©ν•œ 리슀트 μš”μ†Œ μ‚­μ œλŠ” O(N)의 λ³΅μž‘λ„λ₯Ό κ°–κ²Œ λœλ‹€.
  • Z: insert()λ₯Ό 톡해 리슀트 νŠΉμ • μœ„μΉ˜μ— 값을 μ‚½μž…ν•˜λŠ” 것은 O(N)의 λ³΅μž‘λ„λ₯Ό κ°–λŠ”λ‹€.
  • U, D: up, downν•œ 인덱슀λ₯Ό ꡬ해 μ ‘κ·Όν•˜λ―€λ‘œ O(1)이닀.

λ”°λΌμ„œ λ³΅μž‘λ„λŠ” μ΅œμ•…μ˜ 경우 O(N*len(cmd))μ΄λ―€λ‘œ νš¨μœ¨μ„± ν…ŒμŠ€νŠΈμ— ν†΅κ³Όν•˜κΈ°μ—λŠ” 무리가 μžˆλ‹€.


μ—¬κΈ°μ„œ μ μš©ν•  수 μžˆλŠ” μžλ£Œκ΅¬μ‘°κ°€ 더블 λ§ν¬λ“œ λ¦¬μŠ€νŠΈμ΄λ‹€!!


πŸ””λ§ν¬λ“œλ¦¬μŠ€νŠΈ

  1. μ‚½μž…/μ‚­μ œ: λ§ν¬λ“œλ¦¬μŠ€νŠΈμ˜ 경우, μ‚½μž…/μ‚­μ œ μ‹œ 인접 λ…Έλ“œμ˜ 레퍼런슀만 λ³€κ²½ν•΄μ£Όλ©΄ 되기 λ•Œλ¬Έμ— O(1)의 μ‹œκ°„λ³΅μž‘λ„λ₯Ό κ°–κ²Œ λœλ‹€.

  2. 탐색 κ·Έλ ‡λ‹€λ©΄ νƒμƒ‰μ˜ 경우 μ–΄λ–¨κΉŒ? μ΄λŠ” μ–΄λ–€ λ§ν¬λ“œλ¦¬μŠ€νŠΈλƒμ— λ”°λΌμ„œ 달라진닀!

    • μ‹±κΈ€ λ§ν¬λ“œ 리슀트: 인덱슀λ₯Ό 톡해 νƒμƒ‰ν•œλ‹€λ©΄ λ°°μ—΄ νƒμƒ‰μ²˜λŸΌ κ°€μž₯ μ•ž λ…Έλ“œλΆ€ν„° 탐색을 진행해야 ν•˜κΈ° λ•Œλ¬Έμ— O(N)의 μ‹œκ°„λ³΅μž‘λ„λ₯Ό κ°–λŠ”λ‹€.
    • 더블 λ§ν¬λ“œ 리슀트: 인덱슀둜 탐색을 ν•œλ‹€λ©΄ 리슀트의 크기와 νƒμƒ‰ν•˜κ³ μž ν•˜λŠ” index의 크기λ₯Ό λΉ„κ΅ν•˜μ—¬ head, tail 쀑 κ°€κΉŒμš΄ κ³³μ—μ„œ 탐색을 μ‹œμž‘ν•œλ‹€.
    • μ΅œλŒ€ O(N/2)의 λ³΅μž‘λ„λ₯Ό κ°–λŠ”λ‹€.

πŸ‘© λ³Έ 문제λ₯Ό ν’€κΈ° μœ„ν•΄ λ§ν¬λ“œλ¦¬μŠ€νŠΈλ₯Ό 클래슀둜 κ΅¬ν˜„ν•  μˆ˜λ„ μžˆμ§€λ§Œ λ”•μ…”λ„ˆλ¦¬μ— λ§ν¬λ“œλ¦¬μŠ€νŠΈμ˜ νŠΉμ§•μ„ μ μš©ν•˜μ—¬ 탐색을 보닀 λΉ λ₯΄κ²Œ 진행할 수 μžˆλ„λ‘ ν•˜κ³ μž ν•œλ‹€!


πŸ™‹β€β™€οΈμ½”λ“œ: λ§ν¬λ“œλ¦¬μŠ€νŠΈ BY λ”•μ…”λ„ˆλ¦¬

def solution(n, k, cmd):
    table = {i: [i-1, i+1] for i in range(n)} # λ”λΈ”λ§ν¬λ“œλ¦¬μŠ€νŠΈμ²˜λŸΌ μ–‘λ°©ν–₯ λ…Έλ“œμ— λŒ€ν•œ 값을 가지고 μžˆλŠ”λ‹€.
    table[0][0] = None
    table[n-1][1] = None
    answer = ["O"]*n
    storage = []
    cur = k
    
    for c in cmd:
        if c == "C": # ν‘œμ—μ„œ cur행을 μ‚­μ œν•˜κ³  λ‹€μŒ 행을 κ°€λ¦¬ν‚€κ²Œ λœλ‹€. λ§Œμ•½ cur이 λ§ˆμ§€λ§‰ 행일 경우, κ·Έ μ „ λ…Έλ“œλ₯Ό κ°€λ₯΄ν‚¨λ‹€.
            answer[cur] = "X"
            pre, next = table[cur]
            storage.append([pre, cur, next])
            if next == None: # λ§ˆμ§€λ§‰ λ…Έλ“œλƒμ— 따라 cur을 μ—…λ°μ΄νŠΈν•œλ‹€.
                cur = pre
            else:
                cur = next
            if pre == None: # 인접 λ…Έλ“œμ˜ 값듀을 λ³€κ²½ν•œλ‹€.
                table[next][0] = None
            elif next == None:
                table[pre][1] = None
            else:
                table[pre][1] = next
                table[next][0] = pre
        elif c == "Z":
            pre, idx, next = storage.pop() # 졜근 μ‚­μ œν•œ 행에 λŒ€ν•œ 값을 κ°€μ Έμ˜¨λ‹€.
            answer[idx] = "O"
            if pre == None: # 인접 λ…Έλ“œμ˜ 값을 λ³€κ²½ν•œλ‹€.
                table[next][0] = idx
            elif next == None:
                table[pre][1] = idx
            else:
                table[pre][1] = idx
                table[next][0] = idx 
        else:
            c1, c2 = c.split(" ")
            c2 = int(c2)
            if c1 == "U":
                for _ in range(c2):
                    cur = table[cur][0]
            else:
                for _ in range(c2):
                    cur = table[cur][1]
                    
    return "".join(answer)

μ½”λ“œλ§Œ κΈΈ 뿐 μ½”λ“œλŠ” μ‰½κ²Œ 이해할 수 μžˆμ„ 겻이닀!


κ·Έλ ‡λ‹€λ©΄ μ •μ„λŒ€λ‘œ 클래슀λ₯Ό 톡해 λ§ν¬λ“œλ¦¬μŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•΄ 문제λ₯Ό ν•΄κ²°ν•œ μ½”λ“œλ„ ν•œλ²ˆ μ‚΄νŽ΄λ³΄μž!!


πŸ™‹β€β™€οΈμ½”λ“œ: λ§ν¬λ“œλ¦¬μŠ€νŠΈ

class DoubleLinkedList:
    class Node:
        def __init__(self, num, pre):
            self.pre = pre
            self.num = num
            self.next = None
            
    def __init__(self, num, start):
        self.root = self.Node(0, None) # μ—†λŠ” λ…Έλ“œλ₯Ό νƒμƒ‰ν•˜κΈ° μœ„ν•΄ rootκ°’ μ €μž₯
        self.current = None
        self.stack = []
        temp = self.root
        for i in range(1, num):
            new_node = self.Node(i, temp)
            temp.next = new_node
            if i == start:
                self.current = new_node
            temp = new_node
    
    def up(self, num):
        for _ in range(num):
            self.current = self.current.pre
            
    def down(self, num):
        for _ in range(num):
            self.current = self.current.next
            
    def remove(self):
        remove_node = self.current
        self.stack.append(remove_node)
        if remove_node.next:
            self.current = remove_node.next
            self.current.pre = remove_node.pre
            if remove_node == self.root:
                self.root = remove_node.next
            if remove_node.pre:
                remove_node.pre.next = self.current
        else:
            self.current = remove_node.pre
            self.current.next = None
            
    def recover(self):
        recover_node = self.stack.pop()
        if recover_node.pre:
            recover_node.pre.next = recover_node
        if recover_node.next:
            recover_node.next.pre = recover_node
            if recover_node.next == self.root:
                self.root = recover_node
    
    def get_root(self):
        return self.root

            
def solution(n, k, cmd):
    table = DoubleLinkedList(n, k)
    for c in cmd:
        if c == "C":
            table.remove()
        elif c == "Z":
            table.recover()
        else:
            c1, c2 = c.split(" ")
            c2 = int(c2)
            if c1 == "U":
                table.up(c2)
            else:
                table.down(c2)
    node = table.get_root()
    result = ["X"]*n
    while node:
        result[node.num] = "O"
        node = node.next
    return "".join(result)


πŸ‘©β€πŸ’»λ°°μš΄ 점

  • μ‚½μž…, μ‚­μ œ, 탐색이 λ°˜λ³΅λ˜λŠ” λ¬Έμ œμ— λŒ€ν•΄μ„œλŠ” νš¨μœ¨μ„±μ„ λ”°μ Έ 그에 λ§žλŠ” 자료ꡬ쑰λ₯Ό 선택해야 ν•œλ‹€. λ‹¨μˆœνžˆ, κ΅¬ν˜„λ§Œμ΄ μ‰¬μš΄ 것을 νƒν•œλ‹€λ©΄ 효율적인 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μ—†λ‹€.

  • λ§ν¬λ“œλ¦¬μŠ€νŠΈμ˜ λͺ¨λ“  λ…Έλ“œμ—μ„œλŠ” pre, num, next값을 각각 가지고 μžˆμ–΄μ•Ό ν•œλ‹€. μ΄λŸ¬ν•œ 상황같이 클래슀λ₯Ό μ΄μš©ν•œ μΊ‘μŠν™”λ₯Ό 잘 μ‚¬μš©ν•œλ‹€λ©΄ 효율적으둜 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.


πŸ™‡β€β™€οΈν’€μ΄μ— λΆ€μ‘±ν•œ 뢀뢄이 μžˆλ‹€λ©΄ λ§μ”€ν•΄μ£Όμ„Έμš”! κ°μ‚¬ν•©λ‹ˆλ‹€!


πŸ“ƒ μ°Έκ³ 

λŒ“κΈ€λ‚¨κΈ°κΈ°