오늘은 효율적으로 약수를 구하는 방법에 대해 알아보겠습니다. 먼저 기본적인 방법으로 약수를 구해보고, 이후에 좀 더 효율적인 방법으로 접근하여 약수를 구하고 시간 복잡도를 줄이는 과정을 보여드리겠습니다.
어떤 수 𝑁의 약수를 구하는 가장 기본적인 방법은 1부터 𝑁까지 모든 수를 나누어 나머지가 0이 되는 수를 찾는 것입니다. 아래 코드와 같이 1부터 𝑁까지 루프를 돌면 쉽게 결과를 도출할 수 있습니다.
// O(N) 방식 - 비효율적 for (int i = 1; i <= N; i++) { if (N % i == 0) { System.out.println(i); } }
하지만 이와 같은 방법은 모든 수를 검사해야 하므로 큰 수일수록 실행 시간이 오래 걸리며, 앞으로 설명드릴 방법보다 효율적이지 않습니다.
약수는 항상 '쌍'으로 존재한다는 사실을 알면, 어떤 수 𝑁의 약수를 구하기 위해 1부터 √N 까지 수만 나누어 떨어지는지 확인하면 되기 때문에 보다 효율적으로 약수를 구할 수 있습니다. 또한 입력 크기가 커질수록 기존 𝑂(𝑁) 방식보다 훨씬 빠르게 결과를 도출할 수 있습니다.
// O(√N) 방식 - 효율적 for (int i = 1; i * i <= N; i++) { // √N까지만 검사 if (N % i == 0) { // i가 약수라면 System.out.println(i); // 작은 약수 출력 if (i != N / i) { // 중복 방지 (제곱수인 경우) System.out.println((N / i)); // 대응하는 큰 약수 출력 } } }
코드 자체는 for문과 if문으로 작성되었기 때문에 이해하기 어렵지 않으리라 생각됩니다. 하지만 약수의 특징은 단순한 서술만으로 이해하기 어려울 수 있으므로 구체적인 예시를 들어 설명하겠습니다.
(예시) 𝑁 = 36인 경우, 약수의 쌍
(1,36), (2,18), (3,12), (4,9), (6,6)
즉, 약수를 찾을 때 한쪽 약수만 찾으면 다른 약수는 자동으로 구할 수 있음
따라서 √N 까지만 검사하면 모든 약수를 찾을 수 있음
N = 36일 때, √36 = 6 이므로 1부터 6까지만 검사하면 모든 약수를 구할 수 있음
만약 𝑖가 𝑁을 나누어 떨어지게 만들면, 그에 대응하는 𝑁 / 𝑖도 반드시 약수가 됨
N = 36이라면 (6,6)처럼 중복되는 약수가 발생할 수 있음
따라서, i != N / i 조건을 사용하여 중복을 방지함
| 방법 | 시간 복잡도 | 설명 |
|---|---|---|
| 단순 반복문 | 𝑂(𝑁) | 1부터 𝑁까지 모두 검사 |
| √N | O(√N) | 약수의 쌍을 활용하여 빠르게 연산 |