알고리즘/프로그래머스

[프로그래머스] N으로 표현-JAVA

jimkwon 2022. 3. 9. 23:47
반응형

문제 링크

https://programmers.co.kr/learn/courses/30/lessons/42895

 

코딩테스트 연습 - N으로 표현

 

programmers.co.kr

 

Dynamic Programming(동적 계획법)을 이용하여 풀었다.

 

풀이 방식

 

개인적으로 로직을 짜기 제일 까다로웠던 문제중 하나였던 것 같다.

필자는 이 문제를 'N을 사용한 횟수'를 기준으로 나눠 풀이하였다.

즉 N이 5라면, 5를 1번, 2번, 3번...8번(문제 조건에 8보다 크면 -1리턴이기 떄문)사용해서 만들 수 있는 숫자들의 경우로 나눈 것이다.

 

박스가 8개 있다고 해보자.

1번 박스에는 N을 1번 사용해서 만들 수 있는 숫자들이 담겨있다.

N = 5라고 한다면

1번 박스에는 5만 담겨있을 것이다.

2번 박스에서는 어떨까?

5 + 5 , 5 - 5, 5 * 5, 5 / 5, 55   의 경우가 있을 것이다.

즉, 작게 나눠서 생각해본다면, 2번 박스에는 사칙연산(더하기, 빼기, 곱하기, 나누기) 과 N 2개를 사용해서 만들 수 있다.

 

그렇다면 3번 박스는?

5 + 5 + 5,  5 - 5 - 5, 5 * 5 * 5..... 등 일일히 셀 수 가 없는 경우가 나오기 시작한다.

DP는 큰 문제를 작은 단위로 나누는 방식이다. 그렇다면 3번 박스를 어떻게 나눌 수 있을까?

바로 1번 박스와 2번 박스를 합치는 방법이다.

그림과 함께 보자.

 

3개는 (1 + 2) 이므로 1개로 만들수 있는 경우와, 2개로 만들수 있는 경우를 서로 사칙연산해주면 모든 경우가 나올 수 있다.

즉, 박스 1의 원소 5와, 박스 2의 원소 5가지(5+5, 5-5, 5*5, 5/5, 55)를 서로 더하고, 빼고, 나누고, 곱한 모든 경우의 수가 박스 3에 들어가는 것이다.

다만 주의해야 할 점은 (1 + 2)와 (2 + 1)이다. 해당 문제에서는 괄호연산이 가능하기 때문에 5 + 5 * 5와 (5 + 5) * 5가 다르기 때문에 둘의 연산을 반대로 하는 경우도 신경써줘야 한다.

박스 4의 경우는 (1 + 3) (3 + 1) (2 + 2)의 경우와 5555 <- 4번 연속의 5 숫자까지 넣어주면 된다.

이런 방식대로 박스 8까지 모두 만들어준 후, 각 통마다 number와 일치하는 결과값을 count해주면 된다.

 

다시 로직을 간단하게 정리해보자

1. 숫자 N의 개수별로 만들수 있는 박스를 1~8까지 만든다.

2. 박스 숫자가 n인 경우, 더해서 n이 되는 두 숫자의 박스값을 이용해 박스를 채워준다. (ex. 5인 경우 1+5 2+3 3+2 5+1)

3. 각 박스마다 연속된 숫자를 잊지 말고 넣어준다.

4. 각 박스마다 중복된 값이 들어가지 않도록 박스는 Set자료구조를 사용한다.

 

여기서 중요한 부분은 괄호연산때문에 1 + 2와 2 + 1을 둘 다 구해주고, 연속된 숫자(555 등..)를 박스별로 넣어줘야 한다. 또한 박스에 중복된 연산값이 들어가지 않도록 Set 자료구조를 이용해야한다.

 

코드 풀이

 

import java.util.HashSet;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
class Solution {
    //순서쌍 a에 b 합치기
    public void unionSet(Set<Integer> union, Set<Integer> a, Set<Integer> b) {
        for (int n1 : a) {
            for (int n2 : b) {
                union.add(n1 + n2);
                union.add(n1 - n2);
                union.add(n1 * n2);
                if (n2 != 0)
                    union.add(n1 / n2);
            }
        }
    }
    public int solution(int N, int number) {
        List<Set<Integer>> setList = new ArrayList<>();
        
        for (int i = 0; i < 9; i++)
            setList.add(new HashSet<>()); // 개수 별 해쉬셋
        setList.get(1).add(N); //1개로 만들 수 있는 건 나 자신뿐
        if (number == N)
            return 1;
        for (int i = 2; i < 9; i++) {
            for (int j = 1; j <= i / 2; j++) {
                unionSet(setList.get(i), setList.get(i - j), setList.get(j));
                unionSet(setList.get(i), setList.get(j), setList.get(i - j));
            }
            String n = Integer.toString(N);
            setList.get(i).add(Integer.parseInt(n.repeat(i))); //연속된 숫자 넣기
            for (int num : setList.get(i))
                if (num == number)
                    return i;
        }
        return -1;
    }
}

unionSet함수는 N이 3인경우 (1 + 2) (2 + 1)을 해주어 1번 박스와 2번 박스를 사칙연산한 결과를 넣어주는 메소드다. 즉, 박스인 Set을 List에 담아 8개의 Set(박스)가 담긴 리스트를 만들어 로직을 수행하는 것이다.

 

로직에 대해 한참 생각했던 문제..