Programmers_42895 N으로 표현
- TOC {:toc}
이 글은 프로그래머스의 42895번 문제를 풀이한 것을 모아놓은 글입니다.
일종의 연습 기록이며 제가 정답을 받은 코드와 참고할만한 다른 코드를 같이 기록합니다. 필요한 경우 코드에 대한 해설을 기록합니다만 코드는 통과했어도 해설은 틀릴 수 있기 때문에 가볍게 참고해주시길 부탁드립니다. 피드백은 편하신 방법으로 자유롭게 주시면 감사하겠습니다.
2024.04.04
테스트 | 통과 | 시간 | 메모리 |
---|---|---|---|
테스트 1 | 통과 | 0.49ms | 33.5MB |
테스트 2 | 통과 | 0.12ms | 33.6MB |
테스트 3 | 통과 | 0.27ms | 33.5MB |
테스트 4 | 통과 | 55.30ms | 37.7MB |
테스트 5 | 통과 | 9.86ms | 33.8MB |
테스트 6 | 통과 | 0.44ms | 33.4MB |
테스트 7 | 통과 | 0.66ms | 33.4MB |
테스트 8 | 통과 | 9.80ms | 35.8MB |
테스트 9 | 통과 | 0.18ms | 33.5MB |
단계 | 시작 시각 | 끝난 시각 | 걸린 시간 |
---|---|---|---|
문제 이해 | 17:51:27 | 17:52:34 | |
풀이 생각 | 17:52:39 | 17:53:42 | |
코딩 | 17:53:50 | 18:12:26 | |
풀이 생각 | 18:12:31 | 18:21:15 | |
코딩 | 18:21:17 | 18:35:52 | |
디버깅 | 23:04:37 | 23:43:55 |
function solution(N, number) {
const dp = Array.from(new Array(9), () => []);
// N을 나열해서 만드는 경우를 확인 후 각 dp 초기 배열에 포함
for (let i = 0; i < 9; i++) {
const num = Number(`${N}`.repeat(i));
if (num === number) return i;
dp[i].push(num);
}
const operation = {
add: (a, b) => a + b,
sub: (a, b) => a - b,
mul: (a, b) => a * b,
div: (a, b) => Math.floor(Math.max(a, b) / Math.min(a, b)),
};
// N을 2번부터 8번 사용하는 동안
for (let count = 2; count < 9; count++) {
// 이전에 저장한 dp[i]와 dp[count - i]의 값들을 사칙연산해 dp[count]를 구성
for (let i = 1; i <= count / 2; i++) {
for (const a of dp[i]) {
for (const b of dp[count - i]) {
// 위에서 정의해둔 사칙연산을 순회
for (const op in operation) {
// 두 수로 사칙연산한 결과가 number와 일치하면
// 현재 사용된 N의 개수인 count를 바로 반환
const newNum = operation[op](a, b);
if (newNum === number) return count;
if (!dp[count].includes(newNum) && newNum > 0)
dp[count].push(newNum);
}
}
}
}
}
// N을 8번 사용해도 해당되는 수를 만들지 못하면
// -1을 반환
return -1;
}
아이디어 & 풀이
동적 계획법을 사용해 N을 count
번 사용했을 때 만들 수 있는 수를 dp[count]
에 저장해가면서 주어진 number
와 일치여부를 확인한다.
우선 사칙연산 외에 N을 NNN과 같이 단순히 나열해서도 수를 만들 수도 있으며 이 수들을 연산에 포함해주어야 한다.
dp[i]
를 초기화하는 과정에서 N이i
번 반복된 해당 수들을 포함시킨다.- N이
i
번 반복된 수는repeat
메소드를 이용해 구성한다.
- N이
- 이 과정에서
number
가 해당 수와 같을 수 있으므로number
와 일치하는지 확인하고 일치한다면 사용 횟수인i
를 바로 반환한다.- 이 수들은 이후 사칙연산 ‘결과’를 확인하는 과정에서는 (0과는 사칙연산을 하지 않는다) 확인할 수 없으므로 이 때 일치하는지 확인하거나 사칙연산 과정에서 각 수를 확인하는 로직을 수정해야 한다.
사칙연산 결과를 구하는 과정에서 동적 계획법은 다음과 같이 사용한다.
- N을
i
번 사용한 결과값과j
번 사용한 결과값이 있을 때 이 값들을 서로 사칙연산한 결과는 N을i + j
번 사용한 결과값과 같다. 이에 따라 N을 한 번 사용한 결과부터 구하기 시작해 이전에 구한 값들끼리 사칙연산을 하면서 2, 3, 4, … 번 사용했을 때 만들 수 있는 수를 구할 수 있다. - 예를 들어
dp[2]
는dp[1]
의 값들을 상호 사칙연산한 결과로 구성할 수 있다. dp[4]
는dp[1]
의 값들과dp[3]
의 값들을 사칙연산해서 구성할 수도 있지만dp[2]
의 값들을 상호 사칙연산해서 만들 수도 있으므로 두 경우를 모두 포함해야 한다.
이를 위해 다음과 같이 반복문을 구성한다.
- 우선 N을 8번 초과로 쓰면 -1을 반환하므로 사용한 N의 개수인
count
을 2부터 8까지 순회한다.- 한 개 사용한 경우는 사칙연산의 결과값이 아니므로 2부터 시작한다.
- 현재
count
이전의dp[i]
의 조합을 순회한다.- 조합은
dp[i]
와dp[count - i]
로 이루어지며i
가 중간 값을 넘어가면 조합이 반복되므로count / 2
까지만 순회한다.
- 조합은
- 각 조합의 수에 대해
operation
으로 정의한 사칙연산을 순회한다.
반복문 내에서 다음의 로직을 수행한다.
- 사칙연산의 결과가
number
와 같은지 비교하고 같으면 현재count
를 바로 반환한다. - 같지 않으면 현재
dp[count]
에 해당 값을 추가한다.- 이 떄 중복을 피하기 위해 현재
dp[count]
에 해당값이 includes 되어있는지 확인한다. - 음수는 포함되지 않으므로 0보다 큰 값인지도 확인한다.
- 이 떄 중복을 피하기 위해 현재
반복문이 수행되는 동안 number
와 일치하는 수를 찾지 못하면 number
는 N 8개로는 만들 수 없는 수이므로 -1을 반환한다.
디버그
- 사칙연산이 아니라 N을 연속으로 나열해서 만든 수를
dp[i]
의 초기값에는 포함시켰지만 이 경우 아래 반복문에서 해당 수가number
와 일치하는지 검사하지 않게된다.- 그러므로 처음
dp[i]
의 초기값에 해당 수들을 집어넣을 때 해당 수가number
와 일치하는지 검사한 뒤 일치하면 바로N
이 반복되는 횟수인i
를 반환하도록 했다.
- 그러므로 처음
dp[[i]
와dp[j]
를 이용해dp[i + j]
를 구성할 때 시간 단축을 위해 반복을i <= count ** 0.5
까지만 하도록 했는데 경계에 있는 일부 케이스가 포함이 안되는 것 같다.i <= count / 2
로 조건을 수정해서 해결했다.- 생각해보니 애초에 루트값이 아니라 2로 나눈 값 까지 구해야 모든 케이스를 커버할 수 있다.
피드백
중복 제거를 위해 includes
를 이용해 검사하는 대신 Set
을 사용해 add
하면 속도가 훨씬 빨라진다.
테스트 | 통과 | 시간 | 메모리 |
---|---|---|---|
테스트 1 | 통과 | 0.67ms | 33.4MB |
테스트 2 | 통과 | 0.12ms | 33.6MB |
테스트 3 | 통과 | 0.19ms | 33.4MB |
테스트 4 | 통과 | 13.97ms | 37.1MB |
테스트 5 | 통과 | 1.63ms | 33.7MB |
테스트 6 | 통과 | 0.36ms | 33.4MB |
테스트 7 | 통과 | 0.37ms | 33.6MB |
테스트 8 | 통과 | 2.33ms | 35.9MB |
테스트 9 | 통과 | 0.11ms | 33.4MB |
참고 답안
테스트 | 통과 | 시간 | 메모리 |
---|---|---|---|
테스트 1 | 통과 | 0.84ms | 33.7MB |
테스트 2 | 통과 | 0.11ms | 33.7MB |
테스트 3 | 통과 | 0.20ms | 33.5MB |
테스트 4 | 통과 | 19.20ms | 39.7MB |
테스트 5 | 통과 | 10.34ms | 38.2MB |
테스트 6 | 통과 | 0.58ms | 33.5MB |
테스트 7 | 통과 | 0.38ms | 33.6MB |
테스트 8 | 통과 | 18.75ms | 39.7MB |
테스트 9 | 통과 | 0.10ms | 33.5MB |
function solution(N, number) {
const dp = new Array(9).fill(0).map((el) => new Set());
for (let i = 1; i < 9; i++) {
dp[i].add("1".repeat(i) * N);
for (let j = 1; j < i; j++) {
for (const a of dp[j]) {
for (const b of dp[i - j]) {
dp[i].add(a + b);
dp[i].add(a - b);
dp[i].add(a * b);
dp[i].add(Math.floor(a / b));
}
}
}
if (dp[i].has(number)) return i;
}
return -1;
}
- 중복 제거를 위해 배열대신
Set
을 사용했다. - 조건과 반복의 구분을 최소화해 위의 풀이에 비해 훨씬 간호하게 작성했다.
Set
으로 수정한 풀이와 비교했을 때 시간은 비교적 오래 걸리는 편이다.
-
ps-js