Home 프로그래머스 N으로 표현 - 자바스크립트
Post
Cancel

프로그래머스 N으로 표현 - 자바스크립트

📄 문제

프로그래머스 N으로 표현

구현해야 하는 것은

정해진 한 자리 수(N)와 사칙연산을 이용해서 문제에서 요구한 특정 수(number)를 표현할 때 N을 최소로 사용한 횟수를 구해야 한다.

중요한 제한사항은 아래와 같다.

  • 최솟값이 8보다 크면 -1을 return 합니다.

N을 최소 1회, 최대 8회 사용하여 사칙연산자와 조합해서 만든 연산식의 몫 중에 원하는 값이 있는지 확인하면 된다. 최소 횟수를 구해야 하므로 1회 사용했을 때 얻을 수 있는 값을 확인해보고 없다면 2회 사용한 연산을 확인해보고 없다면 3회 사용한 연산을 확인해보는 식으로 8회까지만 진행하자. N이 9번 들어가서 number를 구할 수 있어도 신경쓰지 않아도 된다.

한 자리 숫자와 사칙연산을 이용해서 수를 표현하는 방법은 크게 두 가지다. (1)수학적으로 계산하기(2)문자로 인식 후 이어붙이기다.

(1)은 사칙연산을 말한다. 예로 1을 두 번 사용하여 표현할 수 있는 연산식은 1 + 1, 1 - 1, 1 * 1, 1 / 1이다. (2)는 문자로 인식해서 현재 횟수만큼 반복하면 된다. 1을 두 번 이어붙인 11은 (2)다. 여기서 알 수 있는 것은 (2)의 방식은 횟수별로 하나의 경우의 수만 얻을 수 있다는 것이다.

문제에 나온 예시를 자세히 살펴보자. 5사칙연산(+,-,*,/)를 이용해서 12라는 값을 구해야 한다.

5를 한 번만 사용하면 연산식을 얻을 수 없다. 따라서 (2)의 방법으로 5라는 자기 자신만 얻을 수 있다. 한 번만 사용한 값들 중에는 12가 존재하지 않는다.

이제 5를 두 번 사용할 때 경우의 수를 알아보자. (1)의 방식으로 5 + 5=10, 5 - 5=0, 5 * 5=25, 5 / 5=1, (2)의 방식으로 N을 2번 붙여서 만든 55까지 총 5가지다. 이번에도 12가 존재하지 않는다.

두 번까진 간단하게 생각해볼 수 있지만 세 번부터는 복잡해진다.

5를 세 번 사용한 사칙연산 경우의 수는 아래 표와 같다. 표의 머리글(헤더)은 5를 두 번 사용하여 표현할 수 있는 경우의 수다.

언듯 보면 복잡해보이지만 형광펜으로 강조한 부분을 기준으로 사칙연산이 수행한 경우의 수를 나열한 것이다.

 5 + 55 - 55 * 55 / 555
+(5 + 5) + 5 = 15(5 - 5) + 5 = 5(5 * 5) + 5 = 30(5 / 5) + 5 = 655 + 5 = 60
5 + (5 + 5) = 155 + (5 - 5) = 55 + (5 * 5) = 305 + (5 / 5) = 65 + 55 = 60
-(5 + 5) - 5 = 5(5 - 5) - 5 = -5(5 * 5) - 5 = 20(5 / 5) - 5 = -455 - 5 = 50
5 - (5 + 5) = -55 - (5 - 5) = 55 - (5 * 5) = -205 - (5 / 5) = 45 - 55 = -50
*(5 + 5) * 5 = 50(5 - 5) * 5 = 0(5 * 5) * 5 = 125(5 / 5) * 5 = 555 * 5 = 125
5 * (5 + 5) = 505 * (5 - 5) = 05 * (5 * 5) = 1255 * (5 / 5) = 55 * 55 = 125
/(5 + 5) / 5 = 2(5 - 5) / 5 = 0(5 * 5) / 5 = 5(5 / 5) / 5 = 055 / 5 = 11
5 / (5 + 5) = 05 / (5 - 5) = Inf5 / (5 * 5) = 05 / (5 / 5) = 55 / 55 = 0

5 + 5열의 첫 번째 행((5 + 5) + 5 = 15)과 두 번째 행(5 + (5 + 5) = 15)은 같은 연산자를 같은 횟수로 사용하여 같은 값을 반환하는데 두 번씩 계산하는 이유는 뭘까?

사칙연산에서 더하기(+)와 곱하기(*)는 결합법칙이 성립하지만, 빼기(-)와 나누기(/)는 결합법칙이 성립하지 않기 때문이다. 따라서 연산자 기준으로 위치를 바꾸면 다른 값을 도출한다. 두 번째 행((5 + 5) - 5 = 5)과 여섯번째 행(5 - (5 + 5) = -5)도 같은 연산자를 같은 횟수만큼 사용하지만 다른 값을 반환한다.

결합법칙이란 한 식에서 연산이 두 번 이상 연속될 때, 앞쪽의 연산을 먼저 계산한 값과 뒤쪽의 연산을 먼저 계산한 결과가 항상 같을 경우 그 연산은 결합법칙을 만족한다고 한다.

(2 + 3) + 5 = 2 + (3 + 5) => 결합법칙이 성립한다.

(8 - 7) - 3 ≠ 8 - (7 - 3) => 결합법칙이 성립되지 않는다.

출처 : 위키백과

따라서 N을 두 번 사용한 연산식에 다시 사칙연산을 수행하면 N을 세 번 사용한 경우의 수를 얻을 수 있다. 주의할 점은 연산자 기준으로 양쪽 피연산자 자리를 바꿔서 동일한 연산을 수행해야 한다.

N을 세 번 사용하여 수를 표현하는 경우의 수 =

{ N을 두 번 사용한 경우의 수 }{ +, -, *, / }{ N }

{ N }{ +, -, *, / }{ N을 두 번 사용한 경우의 수 }

N을 네 번 사용한 경우의 수를 구해보자.

N을 네 번 사용하여 수를 표현하는 경우의 수 =

{ N을 세 번 사용한 경우의 수 }{ +, -, *, / }{ N }

{ N을 두 번 사용한 경우의 수 }{ +, -, *, / }{ N을 두 번 사용한 경우의 수 }

{ N을 세 번 사용한 경우의 수 }{ +, -, *, / }{ N }

N을 count만큼 사용한다면 피연산자들의 자리를 바꾸며 모든 결과를 확인해야 한다.

{ N을 count - 1회 사용한 경우의 수 }{ +, -, *, / }{ N을 1회 사용한 경우의 수 }

{ N을 count - 2회 사용한 경우의 수 }{ +, -, *, / }{ N을 2회 사용한 경우의 수 }

{ N을 count - 2회 사용한 경우의 수 }{ +, -, *, / }{ N을 2회 사용한 경우의 수 }

{ N을 1회 사용한 경우의 수 }{ +, -, *, / }{ N을 count - 1회 사용한 경우의 수 }

N을 네 번 사용한 경우의 수가 많아 글이 너무 길어져서 접어둔다.

1️⃣ N을 세 번 사용한 경우의 수(중복 및 Inf 제거)는 [15, 5, 30, 6, 60, -5, 20, -4, 50, -20, 4, -50, 0, 125, 2, 11, 555]로 16가지다.

아래는 세 번 사용한 경우의 수와 N의 사칙연산을 나열한 표다.

사칙연산 별로 첫 번째 행은 { N을 세 번 사용한 경우의 수 } {+, -, \*, / }{ N }, 두 번째 행은 { N을 세 번 사용한 경우의 수 }{ +, -, \*, / }{ N }의 결과에 해당한다.

 15530660-520-450-204-500125211555
+15 + 5 = 205 + 5 = 1030 + 5 = 356 + 5 = 1160 + 5 = 65-5 + 5 = 020 + 5 = 25-4 + 5 = 150 + 5 = 55-20 + 5 = -154 + 5 = 9-50 + 5 = -450 + 5 = 5125 + 5 = 1302 + 5 = 711 + 5 = 16555 + 5 = 560
5 + 15 = 205 + 5= 105 + 30 = 355 + 6 = 115 + 60 = 655 + -5 = 05 + 20 = 255 + -4 = 15 + 50 = 555 + -20 = -155 + 4 = 95 + -50 = -455 + 0 = 55 + 125 = 1305 + 2 = 75 + 11 = 165 + 555 = 560
-15 - 5 = 105 - 5 = 030 - 5 = 256 - 5 = 160 - 5 = 55-5 - 5 = -1020 - 5 = 15-4 - 5 = -950 - 5 = 45-20 - 5 = -254 - 5 = -1-50 - 5 = -550 - 5 = -5125 - 5 = 1202 - 5 = -311 - 5 = 6555 - 5 = 550
5 - 15 = -105 - 5= 05 - 30 = -255 - 6 = -15 - 60 = -555 - -5 = 105 - 20 = -155 - -4 = 95 - 50 = -455 - -20 = 255 - 4 = 15 - -50 = 555 - 0 = 55 - 125 = -1205 - 2 = 35 - 11 = -65 - 550 = -545
*15 * 5 = 755 * 5 = 2530 * 5 = 1506 * 5 = 3060 * 5 = 120-5 * 5 = -2520 * 5 = 100-4 * 5 = -2050 * 5 = 250-20 * 5 = -1004 * 5 = 20-50 * 5 = -2500 * 5 = 0125 * 5 = 6252 * 5 = 1011 * 5 = 55555 * 5 = 2,775
5 * 15 = 755 * 5 = 255 * 30 = 1505 * 6 = 305 * 60 = 1205 * -5 = -255 * 20 = 1005 * -4 = -205 * 50 = 2505 * -20 = -1005 * 4 = 205 * -50 = -2505 * 0 = 05 * 125 = 6255 * 2 = 105 * 11 = 555 * 555 = 2,775
/15 / 5 = 35 / 5 = 130 / 5 = 66 / 5 = 160 / 5 = 12-5 / 5 = -120 / 5 = 4-4 / 5 = -150 / 5 = 10-20 / 5 = -44 / 5 = 0-50 / 5 = -100 / 5 = 0125 / 5 = 252 / 5 = 011 / 5 = 2555 / 5 = 111
5 / 15 = 05 / 5= 15 / 30 = 15 / 6 = 15 / 60 = 05 / -5 = -15 / 20 = 05 / -4 = -25 / 50 = 05 / -20 = 05 / 4 = -15 / -50 = -15 / 0 = Inf5 / 125 = 05 / 2 = 25 / 11 = 05 / 550 = 0

2️⃣ N을 두 번 사용한 경우의 수(중복 및 Inf 제거)는 [10, 0, 25, 1, 55]로 5가지다.

 10025155
10+10 + 10 = 200 + 10 = 1025 + 10 = 351 + 10 = 1155 + 10 = 65
10 + 10 = 2010 + 0 = 1010 + 25 = 1010 + 1 = 1110 + 55 = 65
-10 - 10 = 00 - 10 = -1025 - 10 = 151 - 10 = -955 - 10 = 45
10 - 10 = 010 - 0 = 1010 - 25 = -1510 - 1 = 910 - 55 = -45
*10 * 10 = 1000 * 10 = 1025 + 10 = 351 + 10 = 1155 + 10 = 65
10 * 10 = 10010 * 0 = 010 * 25 = 25010 * 1 = 1010 * 55 = 550
/10 / 10 = 1000 / 10 = 025 / 10 = 21 / 10 = 055 / 10 = 5
10 / 10 = 110 / 0 = Inf10 / 25 = 010 / 1 = 1010 / 55 = 0
0+10 + 0 = 100 + 0 = 025 + 0 = 251 + 0 = 155 + 0 = 55
0 + 10 = 100 + 0 = 00 + 25 = 250 + 1 = 10 + 55 = 55
-10 - 0 = 100 - 0 = 025 - 0 = 251 - 0 = 155 - 0 = 55
0 - 10 = -100 - 0 = 00 - 25 = -250 - 1 = -10 - 55 = -55
*10 * 0 = 00 * 0 = 025 * 0 = 01 * 0 = 055 * 0 = 0
0 * 10 = 00 * 0 = 00 * 25 = 00 * 1 = 00 * 55 = 0
/10 / 0 = Inf0 / 0 = NaN25 / 0 = Inf1 / 0 = Inf55 / 0 = Inf
0 / 10 = 00 / 0 = NaN0 / 25 = 00 / 1 = 00 / 55 = 0
25+10 + 25 = 350 + 25 = 2525 + 25 = 501 + 25 = 2655 + 25 = 80
25 + 10 = 3525 + 0 = 2525 + 25 = 5025 + 1 = 2625 + 55 = 80
-10 - 25 = -150 - 25 = -2525 - 25 = 01 - 25 = -2455 - 25 = 30
25 - 10 = 1525 - 0 = 2525 - 25 = 025 - 1 = 2425 - 55 = -30
*10 * 25 = 2500 * 25 = 025 * 25 = 6251 * 25 = 2555 * 25 = 1,375
25 * 10 = 25025 * 0 = 025 * 25 = 62525 * 1 = 125 * 55 = 1,375
/10 / 25 = 00 / 25 = 025 / 25 = 11 / 25 = 055 / 25 = 2
25 * 10 = 25025 * 0 = 025 * 25 = 62525 * 1 = 125 * 55 = 1,375
1+10 + 1 = 110 + 1 = 125 + 1 = 261 + 1 = 255 + 1 = 56
1 + 10 = 111 + 0 = 11 + 25 = 261 + 1 = 21 + 55 = 56
-10 - 1 = 110 - 1 = 125 - 1 = 261 - 1 = 255 - 1 = 56
1 - 10 = -91 - 0 = 11 - 25 = -241 - 1 = 01 - 55 = -54
*10 * 1 = 100 * 1 = 025 * 1 = 251 * 1 = 155 * 1 = 55
1 * 10 = 101 * 0 = 01 * 25 = 251 * 1 = 11 * 55 = 55
/10 / 1 = 100 / 1 = 025 / 1 = 251 / 1 = 155 / 1 = 55
1 / 10 = 01 / 0 = Inf1 / 25 = 01 / 1 = 11 / 55 = 0
55+10 + 55 = 650 + 55 = 5525 + 55 = 801 + 55 = 5655 + 55 = 110
55 + 10 = 6555 + 0 = 5555 + 25 = 8055 + 1 = 5655 + 55 = 110
-10 - 55 = -450 - 55 = -5525 - 55 = -301 - 55 = -5455 - 55 = 0
55 - 10 = 4555 - 0 = 5555 - 25 = 3055 - 1 = 5455 - 55 = 0
*10 * 55 = 5500 * 55 = 025 * 55 = 1,3751 * 55 = 5555 * 55 = 3,025
55 * 10 = 55055 * 0 = 055 * 25 = 1,37555 * 1 = 5555 * 55 = 3,025
/10 / 55 = 00 / 55 = 025 / 55 = 01 / 55 = 055 / 55 = 1
55 / 10 = 555 / 0 = Inf55 / 25 = 255 / 1 = 5555 / 55 = 1
이렇게 N의 사용 횟수를 1씩 가산하면서 number를 찾아야 한다. 문자열로 이어붙인 값까지 계산해야 하는 것도 잊지말자. 1️⃣의 경우의 수 중 60 / 5 = 12라는 연산이 존재한다. 즉, 5를 네 번 사용하면 12를 얻을 수 있고 가장 적게 사용한 횟수다.

1️⃣ 특정 횟수를 사용했을 때의 경우의 수를 저장할 배열을 선언한다. 이 배열의 각 요소는 Set으로 초기화한다.

중요한 것은 경우의 수 중에 number가 있는지 없는지다. number를 반환하는 연산식의 갯수는 중요하지 않으므로 결과값의 중복을 제거한다. 그러면 다음 횟수의 사칙연산에서 연산 횟수를 줄일 수 있다.

1 ~ 8회만 계산하면 되므로 배열의 크기는 9로 고정한다. i번 사용한 연산의 결과는 arr[i]의 Set에 저장한다. 1번 사용한 결과는 자기 자신뿐이므로 N을 arr[1]에 할당한다.

1
2
3
const MAX_COUNT = 9;
const arr = Array.from({ length: MAX_COUNT }, () => new Set());
arr[1].add(N);

1️⃣ 2 ~ 8회까지 반복하면서 (1)수학적으로 계산하기(2)문자로 인식 후 이어붙이기로 경우의 수를 구한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//(2)문자로 인식 후 이어붙이기
arr[count].add(+N.toString().repeat(count));

//(1)수학적으로 계산하기
//start는 몇 번 사용했는지를 체크하는 인덱스 변수다.
//start가 1일 때 arr[1]와 arr[count - 1]의 사칙연산을 arr[count]에 저장한다.
//start가 2일 때 arr[2]와 arr[count - 2]의 사칙연산을 arr[count]에 저장한다.
let start = 1;
while (start < count) {
  arr[start].forEach((item) => {
    arr[count - start].forEach((item2) => {
      arr[count].add(item + item2);
      arr[count].add(item - item2);
      arr[count].add(item * item2);
      arr[count].add(Math.floor(item / item2));
    });
  });

  //N을 count만큼 사용해서 얻은 경우의 수에 number가 존재하면 종료한다.
  if (arr[count].has(number)) return count;
  start++;
}

🏹 코드

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
const solution = (N, number) => {
  if (N === number) return 1;
  const MAX_COUNT = 9;
  const arr = Array.from({ length: MAX_COUNT }, () => new Set());
  arr[1].add(N);

  let count = 2;
  while (count < MAX_COUNT) {
    let start = 1;
    arr[count].add(+N.toString().repeat(count));
    while (start < count) {
      arr[start].forEach((item) => {
        arr[count - start].forEach((item2) => {
          arr[count].add(item + item2);
          arr[count].add(item - item2);
          arr[count].add(item * item2);
          arr[count].add(Math.floor(item / item2));
        });
      });
      if (arr[count].has(number)) return count;
      start++;
    }

    count++;
  }

  return -1;
};

👩‍🌾 새로 알게 되었거나 중요한 포인트

해당 문제는 동적계획법(DP : Dynamic Programming) 유형의 문제다. 동적계획법은 큰 문제를 작은 문제로 나누어서 푸는 방식의 알고리즘이다. 이 문제에서 작은 문제는 특정 count개의 N으로 만들 수 있는 경우의 수를 배열에 저장(memoization)하는 것이고 이 배열의 요소를 또 다른 count개의 경우의 수를 구하는 데에 사용하여 최종 결과 값인 number를 구하는 것이다.

나한테 이 문제가 꽤 어려워 문제를 해결하고 내용을 정리하는데 3일이 걸렸다… 많은 블로그를 참고했다.

🐝 참고

[프로그래머스] N으로 표현 (DP, 동적계획법) / C++

동적 계획법(Dynamic Programming)

구르미의 개발 이야기 - 프로그래머스 문제 풀이 N으로 표현

작은 한걸음 - [프로그래머스] N으로 표현 Java 풀이

This post is licensed under CC BY 4.0 by the author.

📸 2022-12-12

📸 2022-12-16