티스토리 뷰
https://school.programmers.co.kr/learn/courses/30/lessons/120840
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
📌 문제 설명
머쓱이는 구슬을 친구들에게 나누어주려고 합니다. 구슬은 모두 다르게 생겼습니다. 머쓱이가 갖고 있는 구슬의 개수 balls와 친구들에게 나누어 줄 구슬 개수 share이 매개변수로 주어질 때, balls개의 구슬 중 share개의 구슬을 고르는 가능한 모든 경우의 수를 return 하는 solution 함수를 완성해주세요.
제한사항
- 1 ≤ balls ≤ 30
- 1 ≤ share ≤ 30
- 구슬을 고르는 순서는 고려하지 않습니다.
- share ≤ balls
입출력 예
3 | 2 | 3 |
5 | 3 | 10 |
Hint
서로 다른 n개 중 m개를 뽑는 경우의 수 공식은 다음과 같습니다.
📌나의 풀이
1. 실패한 첫 번째 풀이
function solution(balls, share) {
var answer = 0;
//balls 갖고 있는 구슬 개수 => n
//share 나눠 줄 구슬 개수 => m
//서로 다른 n개 중 m개를 뽑는 경우의 수
//n! / (n-m)! * m! => ! : 팩토리얼은 1부터 주어진 양의 정수까지의 모든 자연수의 곱
//5!=5×4×3×2×1=120 5!는 1부터 5까지 곱셈 5번을 반복
//분자
let n = 1; //n!: 팩토리얼 곱셈 값 저장할 변수 선언
//분모
let m1 = (balls-share); // (n-m)! 값 저장할 변수 선언
let m2 = 1; // m! 값 저장할 변수 선언
let m; // 최종 분모 값 저장할 변수
//분자 팩토리얼 값 계산
for(let i=1; i<=balls; i++){ //내가 가진 구슬 개수 만큼 반복
//예) balls가 3일 때 1*1(i) -> 1*2(i) -> 2*3(i) => 6 (=1*2*3)와 같다.
n *= i;
}
//분모 팩토리얼 계산
for(let k=1; k<=share; k++){ //나눠 줄 구슬 개수 만큼 반복
m2 *= k;
//(n-m)!값 계산
for(let j=1; j<=k-j; j++){ //share-2까지?
//예를들어 3개에서 2개 고른다면 분모 계산은 (3-2)! * 2! => 1:곱셈0번 * (2*1):곱셈1번
//5개에서 3개를 고른다면 (5-3)! * 3! => (2*1):1번 * (3*2*1):2번
//--> 곱셈 반복 개수 규칙이 보임
m1 *= j;
}
}
m = m1 * m2;
answer = n / m;
return answer;
}
처음에 팩토리얼을 까먹어서 검색을 해야했고 문제 이해하고 어떻게 로직을 짜야할지 고민만 30분은 소요한 것 같다.
테스트 코드 실행은 통과! 하지만 채점 결과 35개의 테스트중 4개만 통과고 나머지는 다 실패했다. 😧
문제 원인
- (n-m)! 값을 구하는 로직이 잘못됨. m1은 (balls - share)!의 값을 계산해야 하는데, 이 부분에서 중첩된 for문이 잘못된 방식으로 사용중이었다.
- for(let j=1; j<=k-j; j++){ 이 부분의 조건이 논리적으로 맞지 않았음. 결국 작동하지 않아서 문제! (+ j <= share-2 또한 실패)
해결책:
1. 팩토리얼 계산을 분리하여 구현:
- n!, (n-m)!, m! 팩토리얼을 구하는 코드를 별도의 함수로 만들기
- n! (분자)와 (n-m)! * m! (분모)를 분리하여 계산하기
2. 두번째 풀이
function solution(balls, share) {
var answer = 0;
//balls = n
//share = m
//서로 다른 n개 중 m개를 뽑는 경우의 수 공식 : n! / (n-m)! * m!
//분자
let n = factorial(balls); //n! 값
//분모
let m1 = factorial(balls-share); // (n-m)! 값
let m2 = factorial(share); // m! 값
answer = n / (m1*m2) ; // 경우의 수 공식 계산
return answer;
}
//팩토리얼 계산 함수
function factorial(number){
let result = 1;
for(let i=1; i<=number; i++){
result *= i;
}
return result;
}
수정한 풀이로 테스트 코드는 성공했지만 채점 결과는 실패했다.
정확성: 80.0
합계: 80.0 / 100.0
문제 원인
- 공식 계산 결과가 소수점 이하의 수로 나올 경우에 대해 처리를 안함
예를 들어,이고, m=3일 때 공식의 계산 결과는 10.0이 되고,
이고 m=20m = 20 일 때 공식의 계산 결과는 30,045,015.0이 된다.
JavaScript에서 정수 간의 계산 이라고 하더라도,
큰 수의 나눗셈 에서는 부동소수점 방식으로 처리되기 때문에 소수점 이하의 매우 작은 값이 생길 수 있습니다.
이로 인해 소수점이 생길 수 있으며, 이는 실제로는 계산 오차입니다.
(소수점이 생기는 원인은 부동소수점 오차)
왜 소수점이 발생할까?
- JavaScript의 부동소수점 연산: JavaScript는 IEEE 754 표준을 따르는 64비트 부동소수점으로 숫자를 처리합니다. 따라서 매우 큰 수를 다룰 때는 연산 중 소수점 이하의 작은 오차가 발생할 수 있습니다.
- 팩토리얼 계산에서의 큰 값: 팩토리얼 계산 시, 값이 매우 커지면서 나눗셈 연산이 부정확하게 될 수 있습니다. 결과적으로, 아주 작은 소수점이 추가되어 실제로는 정수여야 할 값에 소수점이 붙는 경우가 발생할 수 있습니다.
해결책:
1. Math.round() 사용
- 보통 부동소수점 오차가 있는 경우 사용하기 적절하다.
- 예를 들어, 9.99999999나 10.0000001처럼 실제로는 정수여야 하는 값이 소수점 자리에서 미세한 오차가 생긴 경우.
구슬 5개 중 3개를 고르는 경우의 수가 10이어야 하지만, 계산 과정에서 10.00000000001이나 9.99999999999처럼 나올 수 있기 때문에 이 경우 Math.round()를 사용해서 이를 10으로 정확히 맞춰줍니다.
Math.floor() 또는 Math.ceil()을 사용하면 계산 결과가 의도적으로 변형될 수 있습니다.
예를 들어, 경우의 수는 정수여야 하므로 floor를 사용하면 실제 경우의 수보다 적은 값으로 처리될 수 있고,
ceil을 사용하면 경우의 수보다 더 큰 값으로 계산될 수 있습니다.
마지막에 리턴 값에 Math.round()를 사용해서 정수로 맞춰주고 통과할 수 있었다.
return Math.round(answer);
📌다른 풀이
1. 재귀함수를 사용하여 팩토리얼 계산
*재귀함수는 자기 자신을 호출하는 함수입니다. 즉, 함수가 자신의 이름을 사용하여 다시 호출되는 방식입니다.
이 방식은 특정 문제를 더 작은 문제로 나누어 해결할 때 유용하게 사용합니다.
const 팩토리얼 = (num) => num === 0 ? 1 : num * 팩토리얼(num - 1)
function solution(balls, share) {
return Math.round(팩토리얼(balls) / 팩토리얼(balls - share) / 팩토리얼(share))
}
'팩토리얼' 함수 동작
- 만약 num이 0이면, 1을 반환합니다. (수학에서 0!=10! = 1이기 때문입니다.)
- 그렇지 않으면, num에 팩토리얼(num - 1)을 곱해 줍니다.
- 예를 들어, 팩토리얼(3)을 호출하면:
- 3이 0이 아니니, 3×팩토리얼(2)3 \times \text{팩토리얼}(2)를 계산합니다.
- 팩토리얼(2)도 0이 아니니 2×팩토리얼(1)2 \times \text{팩토리얼}(1)을 계산하고,
- 팩토리얼(1)도 0이 아니니 1×팩토리얼(0)1 \times \text{팩토리얼}(0)을 계산합니다.
- 팩토리얼(0)이 1이니, 결국 1을 곱해서 3×2×1=63 \times 2 \times 1 = 6을 얻게 됩니다.
- 예를 들어, 팩토리얼(3)을 호출하면:
'프로그래머스' 카테고리의 다른 글
[프로그래머스|JS] Lv.0 외계행성의 나이 (1) | 2024.11.06 |
---|---|
[프로그래머스|JS] Lv.1 문자열 섞기 (1) | 2024.10.30 |
[프로그래머스|JS] Lv.0 짝수 홀수 개수 (0) | 2024.10.16 |
[프로그래머스|JS] Lv.1 K번째수 (0) | 2024.10.14 |
[프로그래머스|Java] Lv.0 배열만들기1 (0) | 2024.10.14 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 오버로딩
- Cleanup
- 카카오로그인
- useEffect
- splice
- 타입스크립트
- NPM
- Async
- 프로젝트회고록
- 티스토리챌린지
- 노마드
- 리액트네이티브
- ts
- ReactJS
- nomard
- await
- 챌린지1일차
- useState
- 프로그래머스
- 재귀함수
- 자바스크립트
- CLI
- React
- slice
- 오블완
- create react app
- props
- 리액트
- TypeScript
- overloading
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함