프로그래머스 - N으로 표현
https://programmers.co.kr/learn/courses/30/lessons/42895
문제 설명
하나의 숫자와 사칙연산으로 특정 수를 만드려고 한다.
5
와 사칙연산으로 12
를 만드는 방법 중 일부이다.
12 = 5 + 5 + (5/5) + (5/5)
12 = 55/5 + 5/5
12 = (55+5) / 5
위에서 볼 수 있듯이, 하나의 숫자 N
으로 목표하는 숫자 number
를 만드는
방법은 여러 가지가 있을 수 있다.
그리고 각각 사용한 숫자의 수는 6
,5
,4
개이며, 최솟값은 4
이다.
사용할 숫자 N
과 만들어야 할 숫자 number
가 주어졌을 때,
N
의 사용횟수의 최솟값을 리턴하자.
제한 사항
- 1 <=
N
<= 9 - 1 <=
number
<= 32,000 - 수식은 사칙연산과 괄호연산만 사용가능하다.
- 나누기에서 나머지는 무시한다.
- 최솟값이 8보다 크면 -1을 리턴한다.
1. DP - O(4^N)
문제를 읽고 느낌이 잘 오지 않는다면, 일단 예시들을 이해해보자.
예시로 주어진 N=2, number=11
에 대해 확인을 해 보도록 하자.
2
를 1개 써서 만들 수 있는 숫자는 무엇이 있을까?
2
하나 뿐이다. 사칙연산은 최소한 하나의 숫자가 더 필요하다.
숫자 하나로는 3
을 만들 수 없다. 숫자를 하나 더 넣어보자.
2
를 2개 써서 만들 수 있는 숫자는 무엇이 있을까?
사칙연산을 쓰지 않고 만들 수 있는 수로는 22
가 있다.
2
를 1
개 써서 만들 수 있는 수 들과
2
를 1
개 써서 만들 수 있는 수 들로
각각 사칙연산을 한 결과를 모으면, 이것도 2
를 2
개 써서 만들 수 있는
수들의 모임이 된다.
22, 2+2, 2-2, 2x2, 2/2
실제로 계산한 값을 넣어도 되지만, 여기서는 수식으로만 남겨두자.
여기의 결과 중에는 3
이 없다. 숫자를 하나 더 넣어봐야 한다.
그럼 2
를 3개 써서 만들 수 있는 숫자는 무엇이 있을까?
사칙연산을 쓰지 않고 만들 수 있는 수로는 222
가 있다.
2
를 1
개 써서 만들 수 있는 수 들과
2
를 2
개 써서 만들 수 있는 수 들로
각각 사칙연산을 한 결과를 모아보자.
2 + (22), 2 + (2 + 2), 2 + (2 - 2), 2 + (2 x 2), 2 + (2 / 2),
2 - (22), 2 - (2 + 2), 2 - (2 - 2), 2 - (2 x 2), 2 - (2 / 2),
2 x (22), 2 x (2 + 2), 2 x (2 - 2), 2 x (2 x 2), 2 x (2 / 2),
2 / (22), 2 / (2 + 2), 2 / (2 - 2), 2 / (2 x 2), 2 / (2 / 2)
갑자기 연산 결과들이 급격하게 늘어났다. 그리고 위의 결과 중 다음 항목을 제거하자. 0으로 나누기는 제외해야 한다.
2 / (2 - 2)
하지만 여기서 끝이 아니다.
2
를 2
개 써서 만들 수 있는 수 들과
2
를 1
개 써서 만들 수 있는 수도 봐야한다.
덧셈과 곱셈은 교환법칙이 성립하지만, 뺄셈과 나눗셈은 교환법칙이 성립하지 않는다.
22 + 2, 22 - 2, 22 x 2, 22 / 2
(2 + 2) + 2, (2 + 2) - 2, (2 + 2) x 2, (2 + 2) / 2
(2 - 2) + 2, (2 - 2) - 2, (2 - 2) x 2, (2 - 2) / 2
(2 x 2) + 2, (2 x 2) - 2, (2 x 2) x 2, (2 x 2) / 2
(2 / 2) + 2, (2 / 2) - 2, (2 / 2) x 2, (2 / 2) / 2
그리고 위의 결과 중, 다음 식으로 11
을 만들 수 있다.
22 / 2
숫자 2
를 3
개 사용해서 3
을 만들었다.
따라서 N=2, number=11
의 답은 3
이다.
위의 과정을 일반화하면 다음과 같다.
숫자 N
을 i
개의 숫자를 써서 만들 수 있는 수들을 찾는다.
먼저, 사칙연산 없이 N
만 나열한 수, NNNNN...
을 만든다.
1
개의 수로 만들 수 있는 숫자와 i-1
개의 숫자로 만들 수 있는
모든 숫자들의 조합애 대해 사칙연산을 수행한다.
2
개의 수로 만들 수 있는 숫자와 i-2
개의 숫자로 만들 수 있는
모든 숫자들의 조합애 대해 사칙연산을 수행한다.
…
i-1
개의 수로 만들 수 있는 숫자와 1
개의 숫자로 만들 수 있는
모든 숫자들의 조합애 대해 사칙연산을 수행한다.
만약 위의 계산결과중에 number
가 있으면 i
가 답이다.
없으면 i+1
번을 써서 만들 수 있는 숫자를 찿아본다.
만약 i
가 9가 되었다면, -1
을 리턴한다.
물론 위의 과정처럼 1 x (i-1), 2 x (i-2), … , (i-1) x 1 이런 식으로 모두 순회할 필요는 없다. 절반정도만 수행하되, 1 x (i-1)에 대해 연산할 때 교환 법칙이 성립하지 않는 뺄셈과 나눗셈에 대해 자리를 바꿔서 한 번 더 수행해주면 연산량을 좀 더 줄일 수 있다.
위의 과정에서 0으로 나누는 일이 없도록 주의하자.
숫자 i
개로 만들 수 있는 모든 숫자들을 기억하고, 이를 i+1
개의 숫자로
만들 수 있는 경우의 수를 구할 때 사용한다. 즉, 이 전에 계산한 결과들을 재활용하여
새로운 해를 구하는데 사용하므로, 이 과정 또한 동적 계획법을 사용한다고 할 수 있다.
위의 과정에서, 비록 다른 연산을 사용했지만 결과는 같은 결과가 나올 수도 있다.
(2 x 2) / 2
는 2
고, (2 + 2) - 2
도 2
다.
연산 결과를 배열등에 보관을 한다면, 중복된 결과에 대해 한번 더 연산을 수행하게 된다. 따라서, 중복된 원소를 허용하지 않는 자료구조에 결과를 담는 것이 좋을 것이다. 여기서는 저장되는 순서는 중요하지 않기 때문에, unordered_set에 저장한다.
Python이라면 set, Java라면 HashSet을 사용하면 좋을 것이다.
그러면 위의 내용을 코드로 표현해보자.
solution.cpp
#include <vector>
#include <unordered_set>
using namespace std;
int solution(int N, int number) {
unordered_set<int> p[8];
int v = 0;
for (int i = 0; i < 8; ++i)
{
v = 10 * v + N;
p[i].insert(v);
for (int j = 0; j < i; ++j)
{
int k = i - j - 1;
for (auto it1 = p[j].begin(); it1 != p[j].end(); ++it1)
{
for (auto it2 = p[k].begin(); it2 != p[k].end(); ++it2)
{
p[i].insert(*it1 + *it2);
p[i].insert(*it1 - *it2);
p[i].insert((*it1) * (*it2));
if (*it2 != 0) p[i].insert((*it1) / (*it2));
}
}
auto it = p[i].find(number);
if (it != p[i].end()) return i + 1;
}
}
return -1;
}
Comments