코테 공부/프로그래머스

[Python] 야간 전술보행 / 두 개의 리스트 같은 기준으로 동시에 정렬

prefer_all 2022. 11. 9. 08:50

문제 설명

전쟁에 참여한 화랑이는 적군의 기지에 침투하여 정보를 훔쳐오는 임무를 받았습니다. 화랑이는 야간 전술 보행을 이용하여 직진하며, 야간 전술 보행은 1m/s의 일정한 속도로 나아갈 수 있습니다. 화랑이의 침입 경로에는 경비병들이 각자 일부 구간들을 감시하고 있습니다. 각각의 경비병들이 감시하는 구간은 서로 겹치지 않으며, 일정 시간 동안 근무 후 일정 시간 동안 휴식을 취하는 과정을 반복합니다. 화랑이가 지나가고 있는 위치를 감시하는 경비병이 근무 중이라면 화랑이는 경비병에게 발각되어 즉시 붙잡히게 됩니다. 하지만 해당 위치를 감시하는 경비병이 휴식을 취하고 있으면 화랑이는 무사히 해당 위치를 지나갈 수 있습니다. 경비병의 근무 정보를 모르는 화랑이는 쉬지 않고 전진을 하며, 화랑이가 출발할 때 모든 경비병들이 동시에 근무를 시작합니다.

 

예를 들어, 적군 기지까지의 거리가 10m이고 2명의 경비병들이 근무를 시작한다고 가정합시다. 첫 번째 경비병의 감시 구간은 화랑이의 출발 위치로부터 3m부터 4m까지이고, 두 번째 경비병의 감시 구간은 화랑이의 출발 위치로부터 5m부터 8m까지입니다. 첫 번째 경비병의 근무 및 휴식 시간은 각각 2초, 5초를 반복하며, 두 번째 경비병의 근무 및 휴식 시간은 각각 4초, 3초를 반복합니다. 이 경우 출발지로부터 화랑이의 위치에 따른 두 경비병의 상태는 아래 표와 같습니다. 첫 번째 경비병이 감시하는 3m ~ 4m 구간을 화랑이는 3초일 때 진입하지만, 첫 번째 경비병은 2초간의 근무를 마치고, 휴식을 시작했으므로, 화랑이는 붙잡히지 않습니다. 하지만 두 번째 경비병이 감시하는 5m ~ 8m 구간에서 화랑이가 8m 지점에 진입했을 때, 두 번째 경비병이 3초간의 휴식을 마치고 근무를 시작하므로 화랑이는 붙잡히게 됩니다.

 

첫 번째 경비병의 상태 근무 근무 휴식 휴식 휴식 휴식 휴식 근무 근무 휴식
두 번째 경비병의 상태 근무 근무 근무 근무 휴식 휴식 휴식 근무 근무 근무
화랑이의 위치 1 2 3 4 5 6 7 8 9 10

 

화랑이의 현재 위치와 적군 기지 사이의 거리를 나타내는 정수 distance, 각 경비병의 감시 구간을 담은 2차원 정수 배열 scope, 같은 순서로 각 경비병의 근무 시간과 휴식 시간을 담은 2차원 정수 배열 times가 주어질 때, 화랑이가 경비를 피해 최대로 이동할 수 있는 거리를 return 하는 solution 함수를 완성하세요.


제한사항

  • 10 ≤ distance ≤ 10,000,000
  • 1 ≤ scope의 길이, times의 길이 ≤ 1,000
    • scope[i]는 i+1번째 경비병이 감시하는 구간입니다.
      • scope[i]를 [a, b]라고 했을 때, (a ≠ b)입니다.
      • scope[i]는 정렬되어 있지 않을 수 있습니다(예시 2번 참조).
    • 경비병의 감시구간은 서로 겹치지 않습니다.
    • 1 ≤ scope의 원소 ≤ distance
    • 1 ≤ times의 원소 ≤ 10
    • times[i]는 i+1번째 경비병의 [근무 시간, 휴식 시간]입니다.

입출력 예

distance scope time result
10 [[3, 4], [5, 8]] [[2, 5], [4, 3]] 8
12 [[7, 8], [4, 6], [11, 10]] [[2, 2], [2, 4], [3, 3]] 12

 

입출력 예 #2

  • 아래의 표는 화랑이의 위치에 따른 세 경비병의 상태를 보여줍니다. 첫 번째 경비병이 감시하는 7m ~ 8m 구간을 화랑이가 지날 때, 첫 번째 경비병은 휴식 중입니다. 두 번째 경비병이 감시하는 4m ~ 6m 구간을 화랑이가 지날 때, 두 번째 경비병은 휴식 중입니다. 세 번째 경비병이 감시하는 10m ~ 11m 구간을 화랑이가 지날 때, 세 번째 경비병은 휴식 중입니다. 따라서 화랑이가 무사히 적군 기지에 침투할 수 있으므로 적군 기지까지의 거리인 12을 return 합니다.

 

첫 번째 경비병의 상태 근무 근무 휴식 휴식 근무 근무 휴식 휴식 근무 근무 휴식 휴식
두 번째 경비병의 상태 근무 근무 휴식 휴식 휴식 휴식 근무 근무 휴식 휴식 휴식 휴식
세 번째 경비병의 상태 근무 근무 근무 휴식 휴식 휴식 근무 근무 근무 휴식 휴식 휴식
화랑이의 위치 1 2 3 4 5 6 7 8 9 10 11 12

풀이

구해야하는 것은 화랑이의 위치에 일하는 경비병의 상태이다.

 

화랑이의 위치에 따른 경비병 별 감시/휴식 여부를 어떻게 계산할까?
휴식 시간은  work+ (work+rest)*N ~ work+ (work+rest)* (N+1) 사이의 값이다.
즉 (화랑이의 위치) % (work+rest) 가 1~work 사이의 값이면 근무, 아니면 휴식중이다.

def solution(distance, scope, times):
    '''
    <input>
    distance: 화랑이의 현재 위치와 적군 기지 사이의 거리
    scope: 각 경비병의 감시 구간 [시작 idx, 끝 idx]
    times: 각 경비병의 휴식 시간 [work, rest sec]
    
    <output>
    화랑이가 경비병을 피해 이동할 수 있는 최대거리
    '''
    answer = 0
    info = [] # 경비병 감시/휴식 정보 append 해냐감
    N = len(times) # 경비병의 수 
    stop = [distance] # 걸리는 경우 - 안 걸리면 distance 반환
    
    for i in range(N): # 경비원을 하나씩 불러옴
        start, end = sorted(scope[i]) # ** 조건: scope[i]는 정렬되어 있지 않을 수 있습니다
        
        # 각 경비원의 감시/휴식 정보 불러옴과 동시에 근무중이면 return
        work, rest = times[i]
        for j in range(start, end+1):
            if 0 < j % (work+rest) <= work:
                stop.append(j)
                
    return sorted(stop)[0]

센스: 

stop = []로 정의하고, stop이 []이면 return distance/ 그렇지 않으면 return sorted(stop)[0] 하는 것보다 

stop = [distance]로 미리 넣어두면 간단함

 

 

시도들

1. 무얼 구하려고 하는 지에 집중해서 쓸 데 없는 것까지 구하지 말자

감시 구간에 해당 경비병이 감시(1) 하는지 휴식(0)하는 지를 담은 list(size가 distance) 만드려고 했으나

화랑이의 위치를 한칸씩 옮기면서 먼저 끝날 수 있으므로 잘못된 풀이방법임을 깨달았다.

 

2. 조건까지 문제를 꼼꼼히 읽자

아래 풀이는 scope와 times를 시간 순으로 정렬한 게 아니므로 모든 경비원을 체크하지 않고 끝난다.

def solution(distance, scope, times):
    answer = 0
    info = [] # 경비병 감시/휴식 정보 append 해냐감
    N = len(times) # 경비병의 수 
    for i in range(N): # 경비원을 하나씩 불러옴
        start, end = sorted(scope[i]) # ** 조건: scope[i]는 정렬되어 있지 않을 수 있습니다
        
        # 각 경비원의 감시/휴식 정보 불러옴과 동시에 근무중이면 return
        work, rest = times[i]
        for j in range(start, end+1):
            if 0 < j % (work+rest) <= work:
                return j
            
    return distance

다른 풀이

dictionary를 사용해서 두 list를 하나의 기준으로 정렬하는 것을 제대로 구현한 풀이이다

 

ex. 두번째 케이스의 경우 scope이 [[7, 8], [4, 6], [11, 10]]으로 주어졌지만 [4,6],[7,8],[10,11] 순으로 확인해야 한다. 

원래 idx를 list에 append하고, dict로 idx를 부여하는 아이디어이다.

 

scope : [[4, 6, 1], [7, 8, 0], [10, 11, 2]]
times_dict: {0: [2, 2], 1: [2, 4], 2: [3, 3]}

def solution(distance, scope, times):
    n = len(scope)
    # scope의 원소들을 정렬한 뒤 순번 append
    for i in range(n) :
        scope[i].sort()
        scope[i].append(i)
    scope.sort() # *** scope 정렬
    
    # 순번 : time으로 하는 딕셔너리 생성
    times_dict = {i : times[i] for i in range(n)}
    
    print(scope)
    print(times_dict)
    
    # 해당 경비병 근무 시간에 걸리면 바로 return 
    for i in range(n) :
        start, end, key = scope[i]
        work, rest = times_dict[key] # *** times_dict[i] 가 아님
        print(work, rest)
        for num in range(start, end + 1) :
            if (num-1) % (work + rest) <= work-1 :
                return num
    
    # 안 걸리면 최대 거리 return
    return distance

 

 

zip을 사용해서 두 list를 하나의 기준으로 정렬하는 것을 구현한 풀이이다

def solution(distance, scope, times):
    scope=list(map(sorted, scope)) #[11, 10]같은거 처리하기 위함.
    scope_times = list(zip(scope, times)) #scope, time을 잠시 합침
    scope_times.sort(key=lambda x: x[0][0]) #감시 구간의 시작이 작은 순으로 정렬
    scope, times = map(list, zip(*scope_times)) #다시 나눔.

    
    for result in range(1, distance + 1):
        now_scope = scope[0]
        now_time = times[0]
        if now_scope[1] < result: #deque쓰면 시간 줄일수 있긴함.
            scope.pop(0)
            times.pop(0)
        
        if not (now_scope[0] <= result and result <= now_scope[1]):
            #scope에 해당안하면 넘어가기
            continue
        
        if (result - 1) % sum(now_time) < now_time[0]:
            return result
    else:
        return distance