CosPro 5회차 9번 - 세 연산
22 Jul 2021문제
정수 number와 target이 주어졌을 때, 다음 세 연산을 이용해 number를 target으로 만들려 합니다.
연산 1. 1을 더합니다.
연산 2. 1을 뺍니다.
연산 3. 2를 곱합니다.
정수 number와 target이 매개변수로 주어질 때, number로 target으로 만들려면 연산을 최소 몇 번 해야 하는지 return 하도록 solution 메소드를 작성해 주세요.
매개변수 설명
두 정수 number와 target이 solution 메소드의 매개변수로 주어집니다.
- number와 target은 0 이상 10,000 이하입니다.
return 값 설명
number를 target으로 만들려면 연산을 최소 몇 번 해야 하는지 return 합니다.
풀이
접근1
전형적인 dp 라 생각하여 dp 배열안에 차례대로 첫 항을 넣고,
다음 값을 만드는 세 연산을 계산하여 점점 항을 늘려가는 식으로 접근하였다.
현재 항(dp[i]
)을 구하는 법을 다음과 같이 나누었다.
- 직전 항에서 더하기 연산 (
dp[i-1] + 1
) - 절반 이전 항에서 곱하기 연산(
dp[i/2] + 1
) - 다음 항에서 빼기 연산(
dp[i+1] + 1
)
이 중 최소값을 찾으면 된다.
위 판별식을 만족하기 위해선 다음 항 이 미리 계산되어 있어야 한다.
이전 항(dp[i]
)들을 계산하고 그 인덱스의 두배에 해당하는 항(dp[i*2]
)에 값을 넣어준다.
쉽게 예시를 들면 6은 3에서 2를 곱한 값이다.
지금 계산하는 항이 3이라 하면 dp[3]
의 값을 구한 상태이다.
그렇다면 3에 [2를 곱하는 연산]을 적용하면 다음 항인 dp[6]
= dp[3] + 1
이 될 것이다.
이렇게 다음 항을 미리 구해 놓을 수 있고, 다음 항에서 1을 뺀 값을 판별할 수 있는 것이다.
코드로 구현하면 다음과 같다.
int[] dp = new int[target+target+1];
for (int i = 0; i < dp.length; i++)
dp[i] = -1;
dp[number+1] = 1;
for (int i = number + 2; i <= target; i++) {
int min = i % 2 == 0 && i/2 >= number
? Math.min(dp[i-1]+1, dp[i/2]+1)
: dp[i-1]+1;
if (dp[i+1] != -1) {
min = Math.min(min, dp[i+1]+1);
}
if (dp[i] != -1) {
min = Math.min(min, dp[i]);
}
dp[i] = min;
dp[i*2] = min + 1;
}
dp 배열의 사이즈는 target 의 2배 길이 만큼 생성한다.
(2 연산에 의해 target2 의 인덱스가 계산이 되므로)
하지만 위의 코드에서는 문제가 생긴다.
1 부터 number 까지의 [-1 빼기 연산] 계산과, 해당 인덱스의 [*2 곱하기 연산] 계산이 누락되었기 때문이다.
예를 들면 다음과 같다. number = 5
, target = 8
을 계산해보자.
(5 - 1) * 2 = 8
이와 같이 빼기 1번, 곱하기 1번으로 계산이 가능하다.
즉 시작점 5에서 뒤로가는 연산 (빼기연산)과 그 값의 곱하기 연산이 미리 계산되어 있어야 한다.
아래는 전체 코드이다.
public int solution(int number, int target) {
// 여기에 코드를 작성해주세요.
int answer = 0;
int[] dp = new int[target+target+1];
for (int i = 0; i < dp.length; i++)
dp[i] = -1;
for (int i = 1; i <= number; i++) {
dp[i] = number - i;
dp[i*2] = dp[i] + 1;
}
dp[number+1] = 1;
for (int i = number + 2; i <= target; i++) {
int min = i % 2 == 0 && i/2 >= number
? Math.min(dp[i-1]+1, dp[i/2]+1)
: dp[i-1]+1;
if (dp[i+1] != -1) {
min = Math.min(min, dp[i+1]+1);
}
if (dp[i] != -1) {
min = Math.min(min, dp[i]);
}
dp[i] = min;
dp[i*2] = min + 1;
}
return dp[target];
}
접근2
다른 방식의 접근이다.
대략적인 접근 방식은 다음과 같다.
- 연산이 사용되는 횟수를
depth
라 칭한다. - 리스트를 생성하고
depth
별로Set
을 추가한다. Set
안은depth
(연산 횟수)에 따라 (미리)계산된 값들이 들어있다.Set
을 활용하여 다음depth
연산에 필요한 연산 값을 재사용 한다. (dp)
그림으로 표현하면 다음과 같다.

그림에서도 나타나듯이 직전 depth
에서 사용된 값이 다음 연산에서 재사용되는 것을 알 수 있다.
코드는 다음과 같다.
public int newSolution(int number, int target) {
if (number == target) return 0;
int depth = 1;
List<Set<Integer>> dp = new ArrayList<>();
Set<Integer> first = new HashSet<>();
first.add(number);
dp.add(0, new HashSet<>());
dp.add(1, first);
while (true) {
Set<Integer> newSet = new HashSet<>();
Set<Integer> preSet = dp.get(depth);
for (Integer preNode : preSet) {
for (int type = 1; type <= 3; ++type) {
int value = 0;
switch (type) {
case 1: value = preNode + 1; break;
case 2: value = preNode - 1; break;
case 3: value = preNode * 2; break;
}
if (value == target) return depth;
else newSet.add(value);
}
}
dp.add(newSet);
depth++;
}
}