Blog

[Algorithm]22 유클리드 호제법 - 최대 공약수와 최소 공배수에대한 잘못된 접근과 수학적 접근을 다시 생각

Author
Summary
무식한 방법과 수학적 방법의 차이
Category
Study
Tags
Algorithm
Favorite
Memory Date
2023/08/22
Progress Status
Done
Cross Reference Study
Related Media
Related Thought
Related Lessons
tag
날짜
작성자
진행상황
진행 전
태그구분
5 more properties

문제 설명

두 수를 입력받아 두 수의 최대공약수와 최소공배수를 반환하는 함수, solution을 완성해 보세요. 배열의 맨 앞에 최대공약수, 그다음 최소공배수를 넣어 반환하면 됩니다. 예를 들어 두 수 3, 12의 최대공약수는 3, 최소공배수는 12이므로 solution(3, 12)는 [3, 12]를 반환해야 합니다.

제한 사항

두 수는 1이상 1000000이하의 자연수입니다.
수학적 사고방식의 부족함을 깨달음
이 문제를 처음 접근 할 때부터 잘못되었다는 것을 느꼈다.
점진적으로 생각하기위해 가장 기본적인 방법들로 접근하는데 코드량이 엄청나게 늘어나면서 내가 컨트롤 할 수 있는 범위를 벗어나기 시작했다.
//3 약수-> 1, 3 //4 -> 1, 2, 4 // n%i == 0인것 //12 약수-> 1, 3, 4, 6, 12 // m%i == 0인것 //실수가 필요함.
Java
복사
3과 12의 약수를 모두 찾아내고, 찾아내기 위해서는 나머지가 0인것을 찾아내야 했다. 나머지가 0인것을 알기위해 double로 실수가 필요했고 약수들을 모두 수집하기 위해서 각각 배열이 필요했다. 배열을 초기화하기위해서는 약수들의 갯수를 알아야했고, 배열에 약수들을 모두 넣어야 했다. 두 숫자의 약수들이 모인 배열에서 약수의 갯수가 적은 길이만큼만 순회하면서 두개의 값이 같은 가장 마지막에 찾아지는 값으로 덮으면 그것이 최대공약수를 구하는 것으로 생각했다. 하지만 이것을 구현하는데 하나하나 너무 많은 코드들이 쌓이기 시작했고, 변수명이나 배열명을 아무리 이해하려고 컨벤션을 맞춰도 작성한 나조차도 직관적으로 알아보기 힘들었다. 결국 실행했지만 어디선가 논리의 문제가 있었고, 다시 고쳐보려했지만 컴파일러는 오류를 나타내지도 않고 그냥 잘못된 값이 나오고 있어서 어느부분의 논리가 틀렸는지 알 수 없는 문제가 발생했다. 아침에 운동을하면서 강의를 들었는데 잠시 선생님이 코드에 대한 가장 위험한 오류가 있는데, 컴파일 단계에서의 오류도 아니고, 예외에 대한 오류도 아니고, 바로 실행은 되는데 잘못된 결과가 나오는 것이라 했다. 이것은 잘못하면 밤을새도 못고친다. 그 경험을 아침에 오자마자 바로 할 줄은 몰랐다. 아직 공배수는 구하지도 않았는데 말이다.
public class Solution { public String Solution(int n, int m){ //3 약수-> 1, 3 //4 -> 1, 2, 4 // n%i == 0인것 //12 약수-> 1, 3, 4, 6, 12 // m%i == 0인것 //실수가 필요함. double doubleN = n; double doubleM = m; int divNCnt = 0; int divMCnt = 0; for(int i=1;i<=n;i++){ if(doubleN%i == 0){ divNCnt++; } } for(int i=1;i<=n;i++){ if(doubleM%i == 0){ divMCnt++; } } int[] nArr = new int[divNCnt]; int[] mArr = new int[divMCnt]; for (int i=0;i<nArr.length;i++){ if(doubleN%i == 0){ nArr[i] = n%i; } } for (int i=0;i<mArr.length;i++){ if(doubleM%i == 0){ mArr[i] = m%i; } } int minLength = 0; if(mArr.length<= nArr.length){ minLength = mArr.length; }else{ minLength = nArr.length; } int sameDivLength = 1; int[] sameDiv = new int[sameDivLength]; for (int i=0;i<minLength;i++){ for(int j=0;j<minLength;j++) { if (nArr[i] == mArr[i]) { sameDiv[0] = nArr[i]; } } } String answer = Arrays.toString(sameDiv); //int[] answer = new int[sameLength]; return answer; } public static void main(String[] args){ Solution sol = new Solution(); int n = 3; int m = 12; System.out.println(sol.Solution(n, m)); } }
Java
복사
ChatGPT를 통한 코드리뷰와 멘토링
나는 시간을 지체하지않고 나의 접근 방법이 일단 잘못되었다는 것을 인지했기 때문에 ChatGPT에게 코드를 대신 작성해달라는 것이 아니라 코드에 대한 접근 방법과 개선할 수 있는 방안을 물어봤다.
나는 이 접근 방법 자체가 무엇인지 궁금했다. 수학적 사고가 부족했던 것인데, 어떤 방식인지 더 이해해야 했다.
공배수에 대한 연관도 필요했다.
나는 우선 내 코드의 접근 방법보다 위 수학적 접근을 활용한 코드를 보고싶었다.
사실 이 정도 코드는 내가 구현을 못하는 코드가 아니다. 아직 익숙하지 않은 Stream등을 사용한것도 아니며, 내가 계속해서 문제를 풀어오던 구문들이다. 그런데 나는 이 방식으로 접근하지 못했다.
나는 유클리드 호제법이라는 최대 공약수와 최소 공배수를 구하는 간단한 논리를 그냥 보고 아 그렇구나가 아니라, 내 문장으로 내가 쓰면서 내 자신이 이해했는지 궁금했다.
해당 접근을 적용한 수정한 코드는 다음과 같다.
package Algorithm25; import java.util.Arrays; /*최대공약수와 최소공배수 문제 설명 두 수를 입력받아 두 수의 최대공약수와 최소공배수를 반환하는 함수, solution을 완성해 보세요. 배열의 맨 앞에 최대공약수, 그다음 최소공배수를 넣어 반환하면 됩니다. 예를 들어 두 수 3, 12의 최대공약수는 3, 최소공배수는 12이므로 solution(3, 12)는 [3, 12]를 반환해야 합니다. 제한 사항 두 수는 1이상 1000000이하의 자연수입니다.*/ public class Solution { public int[] Solution(int n, int m){ // --------------------------잘못된 접근방법을 선택한 나---------------------------- ////3 약수-> 1, 3 ////4 -> 1, 2, 4 //// n%i == 0인것 ////12 약수-> 1, 3, 4, 6, 12 //// m%i == 0인것 ////실수가 필요함. //double doubleN = n; //double doubleM = m; //int divNCnt = 0; //int divMCnt = 0; //for(int i=1;i<=n;i++){ // if(doubleN%i == 0){ // divNCnt++; // } //} //for(int i=1;i<=n;i++){ // if(doubleM%i == 0){ // divMCnt++; // } //} //int[] nArr = new int[divNCnt]; //int[] mArr = new int[divMCnt]; //for (int i=0;i<nArr.length;i++){ // if(doubleN%i == 0){ // nArr[i] = n%i; // } //} //for (int i=0;i<mArr.length;i++){ // if(doubleM%i == 0){ // mArr[i] = m%i; // } //} //int minLength = 0; //if(mArr.length<= nArr.length){ // minLength = mArr.length; //}else{ // minLength = nArr.length; //} // // //int sameDivLength = 1; //int[] sameDiv = new int[sameDivLength]; //for (int i=0;i<minLength;i++){ // for(int j=0;j<minLength;j++) { // if (nArr[i] == mArr[i]) { // sameDiv[0] = nArr[i]; // } // } //} //String answer = Arrays.toString(sameDiv); ////int[] answer = new int[sameLength]; // --------------------------잘못된 접근방법을 선택한 나---------------------------- // --------------------------ChatGPT로부터 유클리드 호제법으로 접근한 나---------------------------- int[] answer = new int[2]; // 매개변수로부터 최대값과 최소값을 구해놓자. int max = Math.max(n, m); int min = Math.min(n, m); // 최대 공약수는 큰값을 작은값으로 나누는데 작은값에서부터 하나씩 줄이다 보면 첫번째로 둘다 나눠지는 값이 있다. 그게 최대 공약수다. //위에 구한 최소값부터 시작, i--로 1씩 줄여나가보자. for (int i = min; i >= 1; i--) { //최대값과 최소값을 동시에(&&) 나눠지는 값이 있을 것이다. 그게 공약수들이고, 그 중 최소값의 최대치부터 시작해서 첫번째 만나서 탈출시키면 최대 공약수만 담은 상태로 나가게 된다. //계속 진행시키면 최소 공약수인 "1"에서 멈추게 된다. 모든 양수 정수의 최소공약수는 1이기 때문에 "break;"를 꼭 첫번째에 탈출 시키자. if (max % i == 0 && min % i == 0) { answer[0] = i; // 최대공약수(GCD) break; } } // 최소 공배수는 두 수를 곱하고 최대공약수로 나누면 된다. 단순하다. 외우자. // 최소공배수(LCM)를 구하기 위해 두 수를 곱하고 최대공약수로 나눕니다. answer[1] = (n * m) / answer[0]; // 최소공배수(LCM) // --------------------------ChatGPT로부터 유클리드 호제법으로 접근한 나---------------------------- return answer; } public static void main(String[] args){ Solution sol = new Solution(); int n = 3; int m = 12; int[] answer = sol.Solution(n, m); System.out.println("[" + answer[0] + ", " + answer[1] + "]"); } }
Java
복사
Stream을 활용한다면?
결과는 잘 나오지만 난 여기서 더 궁금한게 있었다. 팀원이 Stream을 잘 사용해서 나도 공부하려고 하는데 이 문제를 Stream을 활용하면 어떤것들을 사용하는지 보고싶었다. 아직 이해도가 부족해서 내가 직접 작성할 수 없어서 일단 GPT의 코드를 살펴보고 싶었다.
import java.util.stream.IntStream; public class Solution { public int[] solution(int n, int m) { // 최대 공약수(GCD)를 구하기 위해 유클리드 호제법을 활용합니다. int gcd = calculateGCD(n, m); // 최소 공배수(LCM)를 계산합니다. int lcm = (n * m) / gcd; // 최대 공약수와 최소 공배수를 배열로 반환합니다. return new int[]{gcd, lcm}; } // 유클리드 호제법을 사용해 최대 공약수(GCD)를 계산하는 메소드 private int calculateGCD(int a, int b) { return IntStream.rangeClosed(1, Math.min(a, b)) .filter(i -> a % i == 0 && b % i == 0) .max() .orElse(1); } public static void main(String[] args) { Solution sol = new Solution(); int n = 3; int m = 12; int[] result = sol.solution(n, m); System.out.println("[" + result[0] + ", " + result[1] + "]"); } } 위 코드에서 calculateGCD 메소드는 스트림을 사용하여 최대 공약수(GCD)를 계산합니다. IntStream.rangeClosed(1, Math.min(a, b))을 사용하여 1부터 두 수 중 작은 수까지의 범위에서 GCD를 찾습니다. filter를 사용하여 두 수에 대한 공통 약수를 필터링하고, max를 사용하여 가장 큰 공통 약수를 찾습니다. 만약 약수가 없을 경우 1을 반환합니다. 그런 다음, 계산된 GCD를 사용하여 최소 공배수(LCM)를 계산하고, 최종 결과를 배열로 반환하여 출력합니다. 이것은 스트림을 사용하여 최대 공약수(GCD)를 계산한 예제입니다.
Java
복사
우선 Stream이 활용된 부분은 calculateGCD 메소드 내에 구현되어있는데,
rangeClosed(1, Math.min(a,b)) 이부분과
.filter() 메소드
.max() 메소드
.orElse() 메소드가 연결되어있다.
이 부분들을 기억하고 있다가 공부 할 때 잘 지켜봐야 겠다.
특히 .filter()메소드에서 람다식 표현을 집중해서 공부하고 익숙해져야겠다고 생각했다.