a = solution([[1,0,2,0],[6,2,4,0],[0,0,1,0],[6,0,0,4]],1,0)
print(a)
문제 자체는 BFS+순열+조합으로 간단해보였지만, 구현하는데 난이도가 컸던 문제였다.
기본 풀이 자체는 다음과 같다.
카드번호 1,2,3이 있다고 하면
1,2,3을 순열로 먼저 없앨 카드별 순서를 정해준다.
그리고 각 카드는 2장씩 있으므로, A,B 가 있다고 치면
1A->1B
1B->1A는 다를 것이다.
이 모든 경우를 돌려서 결과를 얻는 것이다.
그래서 먼저 나는 card_numbers에 이 보드에 있는 카드번호들을 저장해두고 permutations 모듈을 사용해서 순열을 만들었다 그리고 난뒤, 카드의 앞뒷면을 정해주기 위해 조합을 만들었다.
만약 우리가 카드가 3장이 있다고 하면 이 카드의 앞뒷면을 만들수 있는 가짓수는
000
001
010
011
100
101
110
111
이런식으로 총 8가지가 있을수 있다. 위의 가지수를 각비트로 봐보면 0~7까지인걸 알수 있다. 그러므로 저는 카드의 개수가 N이라고 하면, 2**N 만큼 range를 돌리고, 각 permutations으로 나오는 각각의 카드 index와 비트연산을 해서, 해당 카드의 앞뒷면의 순서를 결정해주었다.
이렇게 한뒤 현재 커서위치 -> 먼저 찾을 카드위치 -> 나중 찾을 카드위치 순으로, bfs를 돌렸다.
bfs를 돌릴때, ctrl 이동과 일반적인 한칸이동을 동시에 시행했다. 범위 밖을 나가던지, 아니면 카드가 있는 위치이던지, 그 위치까지의 길이를 기억해놓은뒤, 그 만큼을 dp라는 visited를 담당하는 행렬에 저장을해주었다. 그리고 우리가 찾는 목적지에 도착하면, return 해주는 방식으로 했다.
이렇게 bfs를 전부 돌린뒤에는 copy한 board에서 우리가 현재 뒤집은 카드들의 정보를 0으로 바꿔주고, 마우스 커서위치는 마지막 카드위치로 바꿔주는 작업을 해준다.
위 풀이 방식은 아슬아슬하게 시간을 통과한지라 여러부분에서 시간을 줄여주었다. 위에서 했듯이 bfs에서 매번 도착지점을 판별해주는 것이 첫번째 방법이었고, distance가 저장된 answer 보다 커지면 탐색을 중단해주는것도 그 방안이었다.
이렇게 구한 answer에 현재 카드의 종류의 2배만큼 더해서 출력해주면된다.
fromcollections import deque
importfunctools
defsolution(board,r,c):
card_dict = [[] for i in range(7)]
card_tuple = set()
forx in range(4):
fory in range(4):
ifboard[x][y]:
card_dict[board[x][y]].append((x,y))
card_tuple.add(board[x][y])
card_tuple = tuple(card_tuple)
dx = [-1,1,0,0]
dy = [0,0,-1,1]
defoutOfrange(x,y):
if0 > x or x >3 or y<0 or y>3:
returnTrue
returnFalse
@functools.lru_cache(maxsize=None)
defbfs(s,t,cards):
ifs == t:
return0
stack = deque()
stack.append((s[0],s[1],0))
visited = set()
whilestack:
x,y,cnt = stack.popleft()
fori in range(4):
repeat = 0
whileTrue:
nx = x + dx[i]*repeat
ny = y + dy[i]*repeat
ifoutOfrange(nx+dx[i],ny+dy[i]) or (repeat != 0 and board[nx][ny] in cards):
break
repeat+= 1
forre in [1,repeat]:
nx = x + dx[i]*re
ny = y + dy[i]*re
ifoutOfrange(nx,ny):
continue
if(nx,ny) == t:
returncnt + 1
if(nx,ny) not in visited:
visited.add((nx,ny))
stack.append((nx,ny,cnt+1))
@functools.lru_cache(maxsize=None)
deffind_card_short_count(cur_position,cards):
iflen(cards) == 0:
return0
min_distance = float('inf')
forpick_card in cards:
position1,position2 = card_dict[pick_card]
remain_cards = tuple(card for card in cards if card != pick_card)
기본적인 풀이는 비슷하다. 대신 이 풀이는 위에서 복잡하게 비트연산을 통해 결정하는 것이 아닌, 재귀함수를 통해, 순열과 조합을 구현했고, functools.lru_cache를 통해 메모라이즈를 해주었다.
이 두 방식을 통해 시간이 획기적으로 줄어들었다.
이 풀이는 크게 2가지의 함수로 나뉠수 있다. find_card_short_count라는 함수와 bfs 함수이다.
bfs는 A->B까지 가는 최소 명령어를 찾아주는 것이다. 여기서 카드의 위치를 판단하는것은 cards라는 tuple을 통해, 있는지 판별을 해준다.
여기서 중요한것은 find_card_short_count라는 함수이다. 이 함수의 역할은 재귀를 통해 순열을 만드는 것이다.
cur_position은 현재 커서의 위치이고, cards는 현재 남아있는 카드들의 번호들이다.
이 cards를 반복문을 돌리면서 그 카드를 제거할 카드라 생각하고, cards에서 현재 선택한 card를 제외한 나머지 카드들을 remain_cards로 남겨준다.
위에서 설명했던 것처럼 한 카드에서도 먼저 제거할 카드를 선택을 해야한다. 그 방법으로 direction1과 direction2로 나뉘어서 해주었다.
한 카드에서 position1 과 position2가 있다고 하면 제거할 수 있는 방법은 두가지이다.
현재 커서위치 -> position1까지의 거리를 구해주고 position1 -> position2 까지의 거리를 구해준다. 그러면 마지막 커서의 위치는 position2가 되고 position2와 remain_cards를 가지고 find_card_short_count를 해주면 된다.
이런 방식으로 현재 커서 위치 -> position2 -> position1도 똑같이 해준다.
문제에서 최고로 놓을 수 있는 사다리의 개수는 3개가 최대이므로, 기본적으로 전체 사다리를 놓을 수 있는 위치를 골라서 재귀로 푸는 방식으로 해줬다. 그리고 result값이 바뀌면 break로 탈출을 해줬다.
사다리의 가로선의 정보를 node-1,node 둘 중 왼쪽인 node-1에 저장해줬다.
그리고 사다리가 놓을 수 없는 조건은 현재 위치인 [가로선][세로선] 과 [가로선][세로선+1] 위치에 전부 0이 존재하면 사다리를 놓을 수 있다고 가정했다. 그리고 난뒤 재귀함수에서 y축을 +2만큼 이동시켜줬다. 그 이유는 해당 가로선에서 내가 고른 세로선에서 사다리를 놓게 되면 세로선 과 세로선+1의 위치에는 사다리를 놓을 수 없다. 그러므로, y를 +2 해주었다.
그리고, 가로선 중에서 주어진 row_index가 같을때에만 그러고 그 다음 가로선부터는 전체 세로선에 대해서 탐색하면 된다.
이렇게 사다리를 추가해주면서, check()라는 함수를 통해, 문제에 주여진 조건인 start_index와 사다리를 통과하고 나온 index가 같은지 비교를 해줘서 True,False 값을 반환시켜주었다.
함수에서 이부분이 교차선을 나타내고, 교차선의 가로선과 세로선을 찾아내서 세로선이 마지막 위치한 것이면 넘어가고,
사다리를 놓을 수 있으면, 사다리를 놓은 뒤, 해당 index에서 +2를 해줘서 재귀함수를 해준다.
구현은 안했지만, 가능한 또 다른 방법은 사다리를 놓을 수 있는 교차선들을 전부 구해놓은 상태에서, 조합을 이용해 사다리를 1개 놓았을 때, 2개 놓았을 때, 3개 놓았을 때,를 구분하고, 고른 사다리가 문제가 없는지 판별해주고, 문제에 조여진 조건대로 시작 index와 도착 index를 같은지 판별하는 방법을 써도 될것 같다.
문제 자체는 어렵게 보이지 않았지만, 실제로 구현하는데에 어려운점이 많았고, 많이 틀렸던 문제였다.
중간중간 가능성이 없는것을 멈춰주고, 시간을 줄이는데 노력해야지 풀 수 있었던 힘들었던 문제였다.