Lv3 프로그래머스(Programmers)[Python][파이썬] 숫자 타자 대회

"""
출처:프로그래머스,
https://school.programmers.co.kr/learn/courses/30/lessons/136797
"""

# 풀이 과정
# 목표: 최소한의 시간으로 타이핑을 하는 경우의 가중치
# 이동하지 않고 제자리 누르기:1 상하좌우 이동 후 누르기:2 대각선:3

# 왼손 오른손 중 가중치가 제일 적은 경우의 수들의 합
# 같은 번호에 두 손가락 놓는거 불가
# 상하좌우 두 번 가는것보다 대각선 한 번이 더 이득
# 최솟값+최솟값 = 최솟값 명제는 항상 성립
# 번호판을 dp로 생각 후 마지막 번호의 dp값이 가중치 최솟값으로 생각접근해보기

# 다시 정리하는 dic.items(): key,value 둘다
# dic.keys(): key 값
# dic.values(): values 모두 키값에 관계없이 모두
from collections import deque


# 왼손 가중치 계산
def left_hand(new_x, new_y):
l = 0
while True:
if new_x > 0 and new_y > 0:
l += 3
new_x -= 1
new_y -= 1
elif new_x > 0 and new_y == 0:
l += 2
new_x -= 1
elif new_x == 0 and new_y > 0:
l += 2
new_y -= 1
elif new_x == 0 and new_y == 0:
l += 1
break
if new_x == 0 and new_y == 0:
break
return l


# 오른손 가중치 계산
def right_hand(new_x, new_y):
r = 0
while True:
if new_x > 0 and new_y > 0:
r += 3
new_x -= 1
new_y -= 1
elif new_x > 0 and new_y == 0:
r += 2
new_x -= 1
elif new_x == 0 and new_y > 0:
r += 2
new_y -= 1
elif new_x == 0 and new_y == 0:
r += 1
break
if new_x == 0 and new_y == 0:
break

return r


# 숫자 위치
def point(k, i):
# i의 번호 찾기
for a in range(4):
for b in range(3):
if k[a][b] == i:
x = a
y = b
break
return x, y


from collections import deque


def solution(numbers):
k = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"], ["*", "0", "#"]]

num = list("123456789*0#")

n = list(numbers)

last = n[-1]

# 이전 가중치
# 이전에 해당번호에 도달했을때 가중치가 가장 적은 경우를 저장한 후 after의 최솟값으로 재갱신
# before={"1":0, "2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"*":0,"#":7}

# m 각 그래프 위치에 따른 가중치

m = [[0] * 12 for _ in range(12)]

# i>j
for i in range(12):
x, y = point(k, num[i])
for j in range(12):
dx, dy = point(k, num[j])
new_x = abs(x - dx)
new_y = abs(y - dy)
m[i][j] = right_hand(new_x, new_y)

w = 0
left = "4"
right = "6"

# 현재 들어있는 손 가중치
now = {}

hand = (left, right)

now[hand] = w

# p: numbers num:위치 인덱스 담은 배열

for c in n:
after = {} # 다음 손 가중치
c_num = num.index(c)
# c: 누를 번호

# h:손 n_w:손 가중치 (이전 손 위치로부터 온 가중치)
# 이전 위치에서 온 가중치들을 저장함과 동시에 도착할 위치중 중복 위치시 최솟값으로 매 번 갱신 그 뿐만 아니라
# 중복된 위치는 dic으로 제거하기에 최대 갯수는 12**2로 유지된다 그 이상으로 늘어나지 않는다
for h, n_w in now.items():
l, r = h # 왼손, 오른손

l_num = num.index(l)
r_num = num.index(r)

if r == c:
if not (l, c) in after.keys() or now[(l, c)] > n_w + 1:
# 이해 과정: or 뒤의 두 번째 조건은 최소값일 경우 재갱신을 위해서이다!
after[(l, c)] = n_w + 1

elif l == c:
if not (c, r) in after.keys() or now[(c, r)] > n_w + 1:
after[(c, r)] = n_w + 1

else:
if not (l, c) in after.keys() or after[(l, c)] > n_w + m[r_num][c_num]:
after[(l, c)] = n_w + m[r_num][c_num]
if not (c, r) in after.keys() or after[(c, r)] > n_w + m[l_num][c_num]:
after[(c, r)] = n_w + m[l_num][c_num]

now = after

return min(list(now.values()))