문제 : 합이 같은 부분집합(DFS)
문제 설명
N개의 원소로 구성된 자연수 집합이 주어지면, 이 집합을 두 개의 부분집합으로 나누었을 때 두 부분집합의 원소의 합이 서로 같은 경우가 존재하면 “YES"를 출력하고, 그렇지 않으면 ”NO"를 출력하는 프로그램을 작성하세요.
둘로 나뉘는 두 부분집합은 서로소 집합(Disjoint Set)이며, 두 부분집합을 합하면 입력으로 주어진 원래의 집합이 되어야 합니다. 예를 들어 {1, 3, 5, 6, 7, 10}이 입력되면 {1, 3, 5, 7} = {6, 10} 으로 두 부분집합의 합이 16으로 같은 경우가 존재하는 것을 알 수 있다.
▣ 입력설명
첫 번째 줄에 자연수 N(1<=N<=10)이 주어집니다.
두 번째 줄에 집합의 원소 N개가 주어진다. 각 원소는 중복되지 않으며, 그 크기는 1,000,000
이하입니다.
▣ 출력설명
첫 번째 줄에 “YES" 또는 ”NO"를 출력한다.
▣ 입력예제 1
6
1 3 5 6 7 10
▣ 출력예제 1
YES
내코드
function solution(arr) {
let answer = "NO";
// 집합의 원소로 구성된 배열 arr 의 인덱스를 값을 의미할 파라미터 i
// 두 가지 부분집합을 나누기 위한 배열 파라미터 lSet, rSet (콘솔에서 확인 및 이해를 돕기 위한 용도)
// 두 가지 부분집합의 합을 저장할 문자열 파라미터 leftN, rightN
// 위 파라미터를 가지는 재귀함수 DFS 선언
function DFS(i = 0, leftN = 0, rightN = 0, lSet = [], rSet = []) {
// 보완가능 부분: 이미 answer 값이 "YES" 라면 이후 불필요한 작업하지 않고 return 처리
// if (answer === "YES") return;
// 모든 원소를 부분집합으로 분리완료 된 경우
if (i === arr.length) {
// 확인을 위한 콘솔 출력
console.log(
`left 집합: ${lSet} 합: ${leftN} | right 집합: ${rSet} 합: ${rightN}`
);
// 두 부분집합의 합이 같다면 정답 문자열에 YES 로 변경
if (leftN === rightN) {
console.log("정답 확인");
answer = "YES";
}
}
// 좌측 부분집합에 i 번째 원소를 집어넣고 재귀함수 호출
else {
DFS(i + 1, leftN + arr[i], rightN, [...lSet, arr[i]], rSet);
// 재귀함수를 통해 좌측 부분 집합(lSet)에 가능한 모든 부분집합의 경우의 수와 우측 부분집합(rSet)에 가능한 모든 부분집합의 경우의 수는 같고
// 두 부분 집합의 합만 구하면 되기 떄문에 좌측 부분집합(lSet)과 우측 부분집합(rSet)을 구분할 필요가 없으므로 우측 부분집합 경우의 수를 제외
if (i > 0)
// 우측 부분집합에 i 번째 원소를 집어넣고 재귀함수 호출
DFS(i + 1, leftN, rightN + arr[i], lSet, [...rSet, arr[i]]);
}
}
DFS();
return answer;
}
풀이
- 인덱스를 값을 의미할 매개변수 i 부분집합을 나누기 위한 배열 매개변수 lSet, rSet 두 가지 부분집합의 합을 저장할 문자열 매개변수 leftN, rightN 를 가지는 재귀함수 DFS 선언
- 좌측 부분집합에 i 번째 원소를 집어넣고 재귀함수 호출하고 우측 부분집합에 i 번째 원소를 집어넣고 재귀함수 호출하면서 가능한 두 부분집합의 경우의 수에 맞게 재귀함수 처리
- 편의상 두개의 부분집합으로 구분했지만 좌측 부분집합(lSet)과 우측 부분집합(rSet)을 구분할 필요가 없으므로 코드상 우측 부분집합 경우의 수를 제외
- 모든 원소를 부분집합으로 분리완료 된 경우에서 두 부분집합의 합이 같다면 정답 문자열에 YES 로 변경
- 재귀함수가 모두 처리된 후 정답 문자열을 반환
보완할 수 있는 부분
// 기존코드
if (i === arr.length) {
// 확인을 위한 콘솔 출력
console.log(`left 집합: ${lSet} 합: ${leftN} | right 집합: ${rSet} 합: ${rightN}`);
// 두 부분집합의 합이 같다면 정답 문자열에 YES 로 변경
if (leftN === rightN) {
console.log("정답 확인");
answer = "YES";
}
}
// 보완코드
// 이미 두 부분집합의 원소의 합이 서로 같은 경우 즉 answer 값이 "YES" 라면 이후 불필요한 작업하지 않고 return 처리
if (answer === "YES") return;
if (i === arr.length) {
// 확인을 위한 콘솔 출력
console.log(`left 집합: ${lSet} 합: ${leftN} | right 집합: ${rSet} 합: ${rightN}`);
// 두 부분집합의 합이 같다면 정답 문자열에 YES 로 변경
if (leftN === rightN) {
console.log("정답 확인");
answer = "YES";
}
}
이미 두 부분집합의 원소의 합이 서로 같은 경우가 나왔다면 추가적으로 부분집합을 구하지 않아도 되므로 answer 값이 "YES" 라면 이후 불필요한 작업하지 않고 return 처리
다른 해결 방법
// 정답지 방법
function solution(arr) {
let answer = "NO",
flag = 0; // 정답을 구했는지 여부 0 : 찾지못한 상태 1 : 이미 찾은 상태
// 집합의 원소로 구성된 배열의 원소들 총합
let total = arr.reduce((a, b) => a + b, 0);
let n = arr.length;
// 배열의 인덱스를 의미할 파라미터 L, 한쪽 부분집합의 총합을 의미할 파라미터 sum
// 위 파라미터를 가지는 재귀함수 DFS 를 선언
function DFS(L, sum) {
// 이미 두 부분집합의 원소의 합이 서로 같은 경우 즉 flas 값이 1 이라면 이후 불필요한 작업하지 않고 return 처리
if (flag) return;
// 모든 원소를 부분집합으로 분리완료 된 경우
if (L === n) {
// 배열의 총합(total)에서 한쪽 부분집합의 합(sum) 을 뺀 값이 sum 과 같다면 두 부분 집합의 값이 같다는 것을 의미
if (total - sum === sum) {
answer = "YES";
flag = 1;
}
} else {
// 한쪽 부분집합에 L 번째 원소를 집어넣고 재귀함수 호출
DFS(L + 1, sum + arr[L]);
// 한쪽 부분집합에 L 번째 원소를 집어넣지 않고 재귀함수 호출
DFS(L + 1, sum);
}
}
DFS(0, 0);
return answer;
}
풀이
- 집합의 원소로 구성된 배열의 원소들을 순회하며 배열원소들의 총합을 total 변수에 초기화
- 배열의 인덱스를 의미할 파라미터 L, 한쪽 부분집합의 총합을 의미할 파라미터 sum 을 가지는 재귀함수 DFS 를 선언
- 한쪽 부분집합에 L 번째 원소를 집어넣고 재귀함수 호출하고 한쪽 부분집합에 L 번째 원소를 집어넣지 않고 재귀함수 호출해 가능한 부분집합들의 합의 경우의 수를 확인
- 모든 원소를 부분집합으로 분리완료 된 경우에 배열의 총합(total)에서 한쪽 부분집합의 합(sum) 을 뺀 값이 sum 과 같다면 두 부분 집합의 값이 같다는 것을 의미하므로 이 경우에 정답 문자열에 YES 로 변경
- 이미 두 부분집합의 원소의 합이 서로 같은 경우 즉 flas 값이 1 이라면 이후 불필요한 작업하지 않고 return 처리
참고
'코딩 테스트 문제 및 풀이' 카테고리의 다른 글
[JS] 코딩 테스트 문제 : 중복순열 구하기 [재귀함수] (0) | 2024.01.29 |
---|---|
[JS] 코딩 테스트 문제 : 바둑이 승차 [DFS] (0) | 2024.01.28 |
[JS] 코딩 테스트 문제 : 부분 집합 구하기 [DFS] (0) | 2024.01.23 |
[JS] 코딩 테스트 문제 : 이진트리 순회 [깊이우선탐색] (0) | 2024.01.22 |
[JS] 코딩 테스트 문제 : 마구간 정하기 [2진 탐색, 결정알고리즘] (0) | 2024.01.19 |