913. Cat and Mouse

Cheol Kang·2020년 9월 5일

meetcode-2020-08-29

목록 보기
2/2

https://leetcode.com/problems/cat-and-mouse

이 문제는 Minimax 의 전형적인? 문제이다.

이 문제를 그냥 풀려고 하면 정말 막막하다. 이 문제를 그래프 문제로 바꾸어보자.

각 Node 의 Value 을 (mouse 의 위치, cat 의 위치, 현재 누구의 trun인지)

turn 은 1일 경우 mouse, 2 일 경우 cat 으로 하자.

그리고 그래프는 밑이라고 했을 때

4---3---1
|   |
2---5
 \ /
  0

(1,2,1)(3,2,2) 와 연결되어 있다. 그리고 (3,2,2)(3,5,1), (3,4,1) 과 연결 되어 있고

(3,5,1)(4,5,2), (3,4,1)(4,5,2) 와 연결 되어 있을 것이다.

이걸 그래프로 표현하면 밑이 된다. 뒷 부분은 생략하였다.

이제 생각해보자. 어떤 Node에서 mouse가 필승을 하기 위해서는 그 node 에서 갈 수 있는 모든 노드가 mouse 에서 필승이여야 한다. 일단 가장 base 상태를 만든다.

  for i in range(N):
      # 어떤경우던 MOUSE 가 0 에 있으면 MOUSE 의 승리
      color_add(0, i, MOUSE, MOUSE)
      color_add(0, i, CAT, MOUSE)
      if i != 0:
          # CAT 과 MOUSE 가 동일한 위치면 CAT 승리
          color_add(i, i, MOUSE, CAT)
          color_add(i, i, CAT, CAT)

이제 이 노드들을 따라 올라가기 위해서 parents 을 구하는 함수을 만든다.

def parents(mouse, cat, trun):
    # 현재의 trun 이 CAT 이면 그전에 MOUSE 가 움직엿음
    if turn == CAT:
        for pre_mouse in graph[mouse]:
            yield pre_mouse, cat, MOUSE
    else:
        for pre_cat in graph[cat]:
            if pre_cat != 0:
                yield mouse, pre_cat, CAT

만약 그 전의 turn 과 현재의 node 의 필승자가 일치하면 그전의 turn 에서 무조건 현재의 node 로 이동하는걸 선택하기 때문에 그 전의 node 도 필승 node 가 된다.

while len(q) > 0:
	  mouse, cat, turn, who_win = q.popleft()
	  # 그 다음 노드는 pre_turn 이 선택
	  for pre_mouse, pre_cat, pre_turn in parents(mouse, cat, turn):
	      # 이미 이 상태에 대해서 누가 이길지 알면 계산 안해도 됨
	      if color[(pre_mouse, pre_cat, pre_turn)] != DRAW:
	          continue
	      # pre_turn 이 다음 노드를 선택하는데, 만약 다음 노드들 중 내가 무조건 이기는 경우의 수가 있다면 그걸 선택하겠지
				# 예를 들어서 현재의 node (mouse, cat, turn) 의 승리자가 mouse 이고, 
				# pre_turn 이 mouse 면 pre_turn mouse 가 무조건 현재의 node 로 움직일 것이다.
	      if pre_turn == who_win:
	          color_add(pre_mouse, pre_cat, pre_turn, who_win)

만약 필승 현재의 어떠한 node 들에서 내 턴에서 이동할 수 있는 모든 node 가 필패 node 밖에 없다면 필패 node 가 된다. 이걸 쉽게 구하기 위해서 모든 child_node 의 수를 미리 구해놓는다.

draw_childs = dict()
for mouse in range(N):
    for cat in range(N):
        # (mouse, cat, MOUSE) 로 부터 갈 수 있는 경우의 수
        # MOUSE 가 움직일 차례이니 graph[mouse] 만큼의 경우가 있다.
        draw_childs[(mouse, cat, MOUSE)] = len(graph[mouse])
        # CAT 은 hole(0) 을 못가기 때문에 그걸 제외한다.
        draw_childs[(mouse, cat, CAT)] = len(graph[cat]) - (1 if 0 in graph[cat] else 0)

만약 draw_child 가 0 이 되었다는건, (pre_mouse, pre_cat, pre_turn) 는 무조건 pre_turn 이 지는 경우의 수 밖에 없다는 것이다. 왜냐하면 q 에는 항상, 필승 또는 필패가 정해진 node 만 들어간다. 만약 필승 node가 있었다면 필승 node 을 선택하였을 것이고, draw child 가 1개라도 있다면 이 경우의 수에 들어가지 않앗을 것이다.

draw_childs[(pre_mouse, pre_cat, pre_turn)] -= 1
# draw 로 가는 경우의 수가 없고, 무조건 내가 이기는 경우의 수도 없다면
# 즉 모든 경우의 수를 다 해봣는데 내가 무조건 이기는 경우의 수도 없고, draw 의 경우의 수도 없다.
if draw_childs[(pre_mouse, pre_cat, pre_turn)] == 0:
    pre_win = CAT if pre_turn == MOUSE else MOUSE
    color_add(pre_mouse, pre_cat, pre_turn, pre_win)

위의 코드를 다 조합하면 밑이 된다. 매우 어렵기 때문에 하나하나 고민하면서 생각해보면 알 수 있다.

시간복잡도는 O(N^3) 이 된다. 총 O(N^2)의 경우의 수(node) 가 존재하고 각 node 들은 최대 N 개의 edge 을 갖기 때문에.

class Solution(object):
    def catMouseGame(self, graph):
        N = len(graph)
        DRAW = 0
        MOUSE = 1
        CAT = 2
        
        def parents(mouse, cat, trun):
            # 현재의 trun 이 CAT 이면 그전에 MOUSE 가 움직엿음
            if turn == CAT:
                for pre_mouse in graph[mouse]:
                    yield pre_mouse, cat, MOUSE
            else:
                for pre_cat in graph[cat]:
                    if pre_cat != 0:
                        yield mouse, pre_cat, CAT
        # (mouse 의 위치, CAT 의 위치, 움직일 차례)
        color = collections.defaultdict(lambda : DRAW)
        q = collections.deque()
				# q 는 항상 필패, 필승이 정해진 node 만 들어간다.
        def color_add(mouse, cat, turn, who_win):
            color[(mouse, cat, turn)] = who_win
            q.append((mouse, cat, turn, who_win))
        ## Base 상태 설정
        for i in range(N):
            # 어떤경우던 MOUSE 가 0 에 있으면 MOUSE 의 승리
            color_add(0, i, MOUSE, MOUSE)
            color_add(0, i, CAT, MOUSE)
            if i != 0:
                # CAT 과 MOUSE 가 동일한 위치면 CAT 승리
                color_add(i, i, MOUSE, CAT)
                color_add(i, i, CAT, CAT)
      draw_childs = dict()
      for mouse in range(N):
          for cat in range(N):
              # (mouse, cat, MOUSE) 로 부터 갈 수 있는 경우의 수
              # MOUSE 가 움직일 차례이니 graph[mouse] 만큼의 경우가 있다.
              draw_childs[(mouse, cat, MOUSE)] = len(graph[mouse])
              # CAT 은 hole(0) 을 못가기 때문에 그걸 제외한다.
              draw_childs[(mouse, cat, CAT)] = len(graph[cat]) - (1 if 0 in graph[cat] else 0)
        
        while len(q) > 0:
            mouse, cat, turn, who_win = q.popleft()
            # 그 다음 노드는 pre_turn 이 선택
            for pre_mouse, pre_cat, pre_turn in parents(mouse, cat, turn):
                # 이미 이 상태에 대해서 누가 이길지 알면 계산 안해도 됨
                if color[(pre_mouse, pre_cat, pre_turn)] != DRAW:
                    continue
                # pre_turn 이 다음 노드를 선택하는데, 만약 다음 노드들 중 내가 무조건 이기는 경우의 수가 있다면 그걸 선택하겠지
                if pre_turn == who_win:
                    color_add(pre_mouse, pre_cat, pre_turn, who_win)
                else:
                    draw_childs[(pre_mouse, pre_cat, pre_turn)] -= 1
                    # draw 로 가는 경우의 수가 없고, 무조건 내가 이기는 경우의 수도 없다면
                    # 즉 모든 경우의 수를 다 해봣는데 내가 무조건 이기는 경우의 수도 없고, draw 의 경우의 수도 없다.
                    if draw_childs[(pre_mouse, pre_cat, pre_turn)] == 0:
                        pre_win = CAT if pre_turn == MOUSE else MOUSE
                        color_add(pre_mouse, pre_cat, pre_turn, pre_win)
                        
        return color[(1, 2, MOUSE)]

0개의 댓글