본문 바로가기

[Project Euler] Problem 026

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.List;
import java.util.ArrayList;
 
public class P026 {
    public static void main(String args[]) {
        System.out.print(run());
    }
    
    public static String run() {
        int remainder;
        int max = -1, ans = 2;
        
        
        for(int d = 2; d <= 1000; d++) {
            List<Integer> list = new ArrayList<Integer>(0);
            remainder = 1;
            
            int diff = 0;
            do {
                remainder = (remainder * 10) % d;
                list.add(remainder);
            } while((diff = list.lastIndexOf(remainder) - list.indexOf(remainder)) == 0);
            
            if(max < diff) {
                max = diff;
                ans = d;
            }
        }
        
        return Integer.toString(ans);
    }
}
cs

 

10 ~ 11행:

변수 선언.  remainder 는 나눗셈 후 나머지가 저장될 변수다.  max 는 순환마디 길이를 비교하기 위한 변수고  ans 는  max 에 해당하는  d , 즉 정답이 저장될 변수다. 

 

14행:

2~1000의 정수  d 에 대해 반복 연산을 진행한다.

 

15 ~ 16행: 

초기값 설정

 

19 ~ 22행:

현재  d 에 대해 나머지 연산을 진행한 후 이 나머지를  list 에 추가한다.

그리고 이 과정을  list 에 같은 나머지 값이 2개 추가될 때까지(나머지가 중복될 때까지) 반복한다.

 lastIndexOf(E e) 는 리스트에서 주어진 요소가 여러 개일 때, 마지막 요소의 인덱스를,

 indexOf(E e) 는 리스트에서 주어진 요소가 여러 개일 때, 첫 번째 요소의 인덱스를 반환한다.

만약 주어진 요소가 하나 뿐이라면 두 메소드는 같은 값을 반환하게 되고,

그렇지 않다면 두 반환 값의 차이( diff )가 순환마디의 길이가 된다.

(엄밀히 말하면 순환마디의 길이는  diff + 1 이지만, +1을 하지 않아도 이전 순환마디의 길이와 비교하는 데에는 문제가 없다.

 3 < 5 나  3 + 1 < 5 + 1 이나 같다는 얘기다.)

 

24 ~ 27행:

이번에 구해진 순환마디의 길이( diff )가  max 보다 크다면  max 에  diff 를 대입한다.

그리고 이 때의  값을  ans 에 저장한다.

 d = 2 ~ d = 1000 까지의 반복을 마치면  max 에는 가장 긴 순환마디의 길이가,  ans 에는 그 때의  값이 저장되어 있다.

 

30행:

위와 같은 과정을 거쳐 구해진  ans  값을 문자열로 변환해 반환한다.

 

 

1/7을 생각해보자.

 

1.

 

2.

 

3.

...

 

1.

1을 7로 나누면 몫은 0, 나머지는 1이 된다.

 

2.

앞의 계산에서 나온 나머지에 10을 곱한다. 그리고 그 수에 대해 다시 나눗셈을 한다.

1을 7로 나눈 나머지에 10을 곱하고 그걸 다시 7로 나눈다.

이 때 몫은 1, 나머지는 3이 된다.

 

3.

앞에서 나온 나머지(3)에 다시 10을 곱하고, 그 수에 대해 나눗셈을 한다.

즉 30을 7로 나누어 몫과 나머지를 계산한다.

몫은 4, 나머지는 2가 된다.

 

 

이런 과정은 결국 다음과 같은 패턴의 반복이다.

 

1. 초기값은 1

2. 1을 7로 나눈다. 몫은 0, 나머지는 1.

3. 앞에서 나온 나머지 1에 10을 곱하고 그 수를 7로 나눈다. 몫은 1, 나머지는 3.

4. 앞에서 나온 나머지 3에 10을 곱하고 그 수를 7로 나눈다. 몫은 4, 나머지는 2.

5. 앞에서 나온 나머지 2에 10을 곱하고 그 수를 7로 나눈다. 몫은 2, 나머지는 6.

...

 

이를 일반화하면 다음과 같다.

1. 초기값은 1

2. 1을 d로 나눈다. 몫은 q1, 나머지는 r1

3. 앞에서 나온 나머지 r1에 10을 곱하고 그 수를 d로 나눈다. 몫은 q2, 나머지는 r2.

4. 앞에서 나온 나머지 r2에 10을 곱하고 그 수를 7로 나눈다. 몫은 q3, 나머지는 r3.

5. 앞에서 나온 나머지 r3에 10을 곱하고 그 수를 7로 나눈다. 몫은 q4, 나머지는 r4.

...

 

 

뭘 반복해야 할 지는 알았다. 이제 언제까지 반복해야 하는지만 알아내면 된다.

나에게 필요한 건 순환마디고, 순환마디는 소수점 아래의 숫자들로 이루어진다.

그리고 소수점 아래의 수는 위 과정에서 나오는 몫이다.

따라서 나눗셈을 진행하면서 이전에 나왔던 몫이 또 나오면 반복을 종료하면 된다.

 

...고 생각했으나 해보니 그렇지 않았다.

가령 1/998은 0.00100200400801603206412825651303...인데, 만약 몫의 중복으로 순환마디를 구분한다면 1/998의 순환마디는 00이 된다. 어떻게 순환마디를 구분할 수 있을까?

 

몫이 아닌 나머지의 중복으로 구분하면 된다. 즉 이전에 나왔던 나머지가 또 나오면 반복을 종료하는 것이다. 실질적으로 다음 몫에 영향을 미치는 것은 나머지기 때문이다.

 

1/7을 다시 보자.

1. 초기값은 1

2. 1을 7로 나눈다. 몫은 0, 나머지는 1.

3. 앞에서 나온 나머지 1에 10을 곱하고 그 수를 7로 나눈다. 몫은 1, 나머지는 3.

4. 앞에서 나온 나머지 3에 10을 곱하고 그 수를 7로 나눈다. 몫은 4, 나머지는 2.

5. 앞에서 나온 나머지 2에 10을 곱하고 그 수를 7로 나눈다. 몫은 2, 나머지는 6.

6. 앞에서 나온 나머지 6에 10을 곱하고 그 수를 7로 나눈다. 몫은 8, 나머지는 4.

7. 앞에서 나온 나머지 4에 10을 곱하고 그 수를 7로 나눈다. 몫은 5, 나머지는 5.

8. 앞에서 나온 나머지 5에 10을 곱하고 그 수를 7로 나눈다. 몫은 7, 나머지는 1

9. 1을 7로 나눈다. -> 과정 2와 같다! 

 

과정 9를 유심히 보자. 1을 7로 나누게 된다. 이는 맨 처음 했던 연산이므로 여기서 새로운 순환마디가 시작된다. 그럼 왜 처음에 했던 연산을 하게 된 것일까?

과정 8에서 나머지가 1이 나왔기 때문이다. 왜 과정 8에서는 나머지가 1이 된 걸까?

과정 7에서 나머지가 5가 나왔기 때문이다. 그리고 과정 7에서 나머지가 5가 된 이유는, ....

 

즉 나머지가 순환마디의 끝과 시작을 결정한다고 볼 수 있다.

과정 9는 과정 2에서 했던 것과 동일하다. 1을 7로 나누면 몫 1과 나머지 0을 얻게 되고, 이제 과정 3 ~ 과정 8을 반복하게 된다.

 

따라서 몫의 패턴, 즉 순환마디의 시작과 끝을 결정하는 것은 몫이 아닌 나머지다. 

그래서 코드 상에서 몫을 구하지도 않았고 22행의 반복문 탈출 조건은 나머지의 중복으로 한 것이다.

'Programming > Solutions' 카테고리의 다른 글

[BAEKJOON] 1004번  (0) 2021.03.05
[Project Euler] Problem 022  (0) 2021.02.13
[Project Euler] Coding Quiz 2  (0) 2021.01.20
[Project Euler] Problem 005  (0) 2021.01.16
[Project Euler] Coding Quiz 4  (0) 2021.01.15