[Python] 코딩 도장 - Josephus Problem

Updated:

코딩 도장 사이트의 문제를 직접 풀어본 내용을 정리하여 올립니다.

코딩 도장에서 여러 문제를 확인할 수 있습니다.

난이도 순으로 차근차근 풀어보려 합니다.


[문제: Josephus Problem] - Lv.2

약 2,000년 전에는 전쟁에서 병사들이 적들에 의해 동굴에 갇히게 되는 경우가 종종 있었다고 한다.

포로가 되는것을 방지하기 위해 동그랗게 서서 마지막 한 사람이 남을 때 까지 순서대로 돌아가며 세번째에 해당되는 사람을 죽였다.

마지막으로 남게되는 사람은 자살하기로 약속되어 있었지만 보통 적들에게 항복을 하는 경우가 많았다고 한다.

풀어야 할 문제는 총 인원수(N)와 간격(K)이 주어졌을 때 가장 마지막에 살아남는 병사의 위치를 알아내는 것이다.

예를 들어 병사수가 총 10명이고 돌아가며 세번째에 해당되는 병사를 제거하는 경우는 다음과 같다:

N = 10, K = 3

위의 경우 다음과 같은 순서로 병사들이 제거된다. (괄호는 제거되는 병사를 의미한다)

1st round: 1 2 (3) 4 5 (6) 7 8 (9) 10
2nd round:                            1 (2) 4 5 (7) 8 10
3rd round:                                                (1) 4 5 (8) 10
4th round:                                                               4 (5) 10
5th round:                                                                        4 (10)

위 예에서 끝가지 살아남는 병사는 4, 즉 4번째 병사이다.

입력 데이터는 총 병사수 N과 간격 K이다.

출력 데이터는 마지막까지 살아남는 병사의 위치이다.

(단, 최초 시작은 1번 병사부터이다.)

입출력 예는 다음과 같다:

initial data:
10 3

answer:
4

출처: https://codingdojang.com/scode/447?answer_mode=hide


[풀이]

def Josephus_Problem():
    n, k = [int(i) for i in input("initial data:").split()]
    a = list(range(1,n+1))

    while a[-1] != a[-2]:
        del_v = [v for i,v in enumerate(a) if (i+1) % k == 0]
        a = a + sorted(list(set(a)-set(del_v)))
        
    return print("answer:", a[-1])

Josephus_Problem()
initial data:10 3
answer: 4

이 문제는 간격 k가 매 차례마다 리셋이 되는게 아니라서 전 차례의 나머지를 고려해야한다.

처음엔 간격 k로 나눠지는 인덱스를 제거하고 나머지를 계산해 다음 차례에 인덱스에 나머지를 플러스 하는 방식을 생각했다.

그런데 만약 간격이 4인데 2개가 남았다면 다시 2개를 추가한후 4번째가 제거 될 것이다.

이런 이유 때문에 문제 예시를 응용해 리스트를 추가하는 방식으로 작성하였다.

[v for i,v in enumerate(a) if (i+1) % k == 0]를 통해 매 차례 제거되는 병사의 값을 구한다.

sorted(list(set(a)-set(del_v)))로 남은 병사를 구한 후 원래 리스트에 추가해준다.

이 방법을 사용하지 않고 단순히 나누기만 하면 원하는 형태로 리스트에 추가하기가 어렵다.

반복을 계속해서 리스트의 마지막 숫자와 그 앞 숫자가 같으면 종료한다.

def Josephus_Problem():
    n, k = [int(i) for i in input("initial data:").split()]
    a = list(range(1,n+1))

    while a[-1] != a[-2]:
        del_v = [v for i,v in enumerate(a) if (i+1) % k == 0]
        a = a + sorted(list(set(a)-set(del_v)))
        print(sorted(list(set(a)-set(del_v))))
    return print("answer:", a[-1])

Josephus_Problem()
initial data:12 4
[1, 2, 3, 5, 6, 7, 9, 10, 11]
[1, 2, 3, 6, 7, 9, 11]
[1, 2, 6, 7, 9]
[1, 2, 6, 9]
[1, 2, 9]
[1, 2]
[1, 2]
[1]
[1]
answer: 1

앞서 예를 들었던대로 마지막에 1,2가 2번 나오는 케이스가 있다.


[추천 풀이]

def Josephus(n,k):
    a = list(range(1,n+1))
    m = k - 1
    b = 0
    while len(a) > 1:
        b = ((len(a)+b)%k)
        del a[m::k]
        m = k - b - 1
    
    return a

print(Josephus(10,3))
[4]

일단 너무 간결해보여서 마음이 아프다.

이 분은 제거하면서 매 차례마다 시작 인덱스를 바꿔주는 방식을 사용했다.

def Josephus(n,k):
    a = list(range(1,n+1))
    m = k - 1
    b = 0
    while len(a) > 1:
        b = ((len(a)+b)%k)          # 매 차례 나머지
        print(a, "시작 인덱스:", m)
        del a[m::k]
        m = k - b - 1
        
    return a

print(Josephus(12,4))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 시작 인덱스: 3
[1, 2, 3, 5, 6, 7, 9, 10, 11] 시작 인덱스: 3
[1, 2, 3, 6, 7, 9, 11] 시작 인덱스: 2
[1, 2, 6, 7, 9] 시작 인덱스: 3
[1, 2, 6, 9] 시작 인덱스: 2
[1, 2, 9] 시작 인덱스: 2
[1, 2] 시작 인덱스: 3
[1, 2] 시작 인덱스: 1
[1]

여기서 1,2를 보면 시작 인덱스가 3인데 나는 이 경우 인덱스가 없기에 오류가 뜰거라 예상했다.

그래서 굳이 리스트를 계속 더해주는 방식을 사용하였는데 이 분은 결과가 잘 나온다.

a[m::k] 코드에서 [시작:끝:증감]인건 알고 있는데 오류가 안생겨 자세히 확인하려 한다.

a=[1,2]
print(a[0::4])
print(a[1::4])
print(a[2::4])
print(a[3::4])
print(a[6::4])
[1]
[2]
[]
[]
[]

보면 시작이 0,1인 경우는 당연히 잘나타나는데 3,6인 경우 그냥 빈 값을 반환한다.

원래 리스트의 길이보다 큰 인덱스를 사용해도 아무런 문제가 없다.

그래서 제거 방식으로 슬라이싱을 사용하신 것 같다.

n,k=list(map(int,input().split()))
s = list(range(1,n+1))
cursor = 0
while len(s)>1:
    cursor = (cursor+k-1)%len(s)
    s.pop(cursor)
    print(s)
12 4
[1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12]
[1, 2, 3, 5, 6, 7, 9, 10, 11, 12]
[1, 2, 3, 5, 6, 7, 9, 10, 11]
[1, 2, 3, 6, 7, 9, 10, 11]
[1, 2, 3, 6, 7, 9, 11]
[1, 2, 6, 7, 9, 11]
[1, 2, 6, 7, 9]
[1, 2, 6, 9]
[1, 2, 9]
[1, 2]
[1]

또 다른 풀이 방식인데 이분은 매 차례마다 여러 숫자를 지우는게 아니라 하나씩 지워나간다.

정말 참신한 생각이 많다..

Leave a comment