오랜만에 devlog에 글을 남기게 되었습니당. 브런치도 velog도 github도 고민해봤는데 그래도 여기다가 차근 차근 올린 뒤에 여력이 될 때 블로그를 전반적으로 수정해볼 예정쓰… 근데 velog에도 적긴 해야할 거 같아요..
우연치 않게 완전 제곱수를 구해야 하는 문제를 풀게 됬습니당. (물론 다짜고짜 완전제곱수를 구하라는 건 아니었고 문제 풀이를 하다 보니 완전제곱수의 개수를 구하는 문제…)
그래서 처음엔 단순히 약수의 개수를 구해서 개수가 홀수인 수를 찾으려 했습니다.
const isOddDivisorCount = (num: number): boolean => {
let count: number = 0
for (let i = 1; i <= num; i++) {
if (num % i === 0) count++
}
return count % 2 === 0 ? false : true
}
위 함수를 실행 시켜보면 올바른 결과값을 추출해 내긴 했는데…
하지만 이 함수의 실행 시간이 오류에서 걸렸습니다 ㅠㅠ 당연히 1~N까지의 개수를 찾아야 하다보니 N까지 저 함수를 돌리면 N*N이므로 O(n^2)의 최악의 탐색 방법..
그렇다면 홀수 개수인 걸 구지 for문을 다 돌지 않고도 찾아내야 하는 방법이 필요했고 9의 약수의 개수까지 계산했을 때 쯤 x = n^2 형태의 숫자는 홀수개수란 것을 알게 되었습니다.
약수의 개수를 구하면 보통 d = n * x/n (n은 1씩 증가, x/n은 정수) 이런 형태?? 가 될텐데 (오랜만에 써보는 식이라서.. 알파벳이 생각이 안나네융..)
즉 1 x n, 2 x n/2 이렇게 약수가 쌍으로 생기다가 d = x/d일 경우엔 약수가 n 1개만 생기게 됩니다!!! 그러므로 약수의 개수가 홀수이면 완전제곱수!!
그래서 다음 풀이 함수를 만들어봤습니당
export const isPerfectSquareNumber = (num: number): boolean =>
Number.isInteger(Math.sqrt(num))
딱봐도 for 없어지고 훨씬 더 간편해보이는 함수!! 다른 사례들을 보니까 sqrt % 1의 결과를 0과 비교 연산하시더라구요! 그게 더 코스트가 적게드는 방법일까요..? mocha로 돌릴때는 똑같은 것 같던데..
아무튼 실행 결과를 보면
처음 풀었던 함수는 숫자가 커지면서 실행시간에 큰 이슈가 생기게 되었습니다. 하지만 두번째 풀이는 저 5개의 숫자를 돌려도 5ms로 측정되었습니다.
음..하지만 제가 마지막으로 제출한 이 함수도 해당 테스트 조건에 통과하지는 못했어요..아무리 이렇게 해도 N의 크기가 무수히 크다면 O(n)으로는 이슈가 발생하게 되는 거 같아요!
그렇다면 O(logN)의 형태를 생각해봐야했는데.. 이건 시간내에 생각하진 못했고 오늘 아침 샤워하다가 생각난 풀이 방법입니다!
간단하게 설명을 하자면 제곱수는 1,4,9,16,25 의 형태로 증가하게 됩니다. 숫자를 잘보시면 처음엔 3, 그다음엔 5, 그다음엔 7..
이라는게 떠올랐고, for문의 증감식에 이것을 적용하면, N까지 모든 숫자를 셀 필요도 없으며 N이 커질 수록 건너뛰는 숫자도 많아지게 됩니다 즉 O(logN)의 형태로 나올 수 있다는 생각이 들게 되었꼬 그게 사실 이 포스트를 작성하게 된 이유입니당
const isPerfectSquare = (num: number): number => {
let count = 0
let i = 1
let d = 1
while (i <= num) {
count++
i = i + 3 + (d - 1)
d = d + 2
}
return count
}
이 코드와 아까 구한 완전제곱수 확인함수를 for loop 돌려본다면
상당히 유의미한 시간 단축을 확인 할 수 있었습니다.
하지만 어.. 숫자가 어디서 많이 본 숫자에요.. 25611.. 25611*25611 = 655923321입니다.. 확인해보면.. 1까지는 1개 4까지는 2개 3까지는 3개 4까지는 4개.. 사실.. N = n*n이라면
N까지의 완전제곱수는 n*n (n=1,d=1) 의 형태로 가기 때문에 n개만큼의 완전제곱수를 가지는 거였습니다..
export const getCountPerfectSquare = (num: number): number =>
Math.floor(Math.sqrt(num))
사실은.. 답을 올려두고 싶은 마음보다는 내가 어떠한 과정에서 더 쉽게 접근할 수 있는 걸 빙빙 돌아갔는지, 그 과정 속에서 느낀 점은 무엇인지를 적어두고 싶었습니당..
처음은 너무 단순하게 접근했고, 그 단순한 접근 법에서 어떻게 하면 효율적으로 계산량을 줄일지 고민하다 보니 너무 for문을 제거하는 거에만 집중했었던 것 같아요..
좀 더 여유를 갖고 결과값을 봤다던지, 완전제곱수에 대해 파악을 했었다면, 아마도 두번째 함수가 나왔을 때 풀이가 끝났을 것 같아요..
그래도 참 단순한 문제였지만 최악의 방법부터 점점 차선책으로 퀄리티를 올리고, 결국엔 최선의 방법을 찾아낸 경우였어서 이렇게 정리 해봅니당
물론 보시자마자 풀으신 분들도 계시겠지만.. 아무튼…