Post

[programmers-lv2] 완전범죄

programmers lv2 완전범죄 문제를 JAVA를 이용해 bruteforce, dp로 해결하는 방법을 설명합니다.

[2025 프로그래머스 코드챌린지 2차 예선] 완전범죄

🙋‍♂️ 들어가며

이 문제는 A도둑, B도둑이 존재하는데 각각 누적 흔적의 갯수가 n, m보다 작은 상황에서 훔치며 최종적으로 A도둑이 남긴 누적 흔적횟수를 구해야 한다.

2가지 방법으로 구할 수 있겠다

  • bruteforce
  • DP

bruteforce 접근법

  • A가 훔치면 B는 못 훔치고
  • B가 훔치면 A는 못 훔치고

즉 O, X 식으로 만들어보자

testcase

1
2
3
4
5
info = { {1,2}, {2,3}, {2,1} }
n = 4;
m = 4;

result = 2

먼저 처음은 누구도 어떤 것도 훔치지 않았기에 DP[0][0] = 0

1
2
3
4
5
6
{
    {o, x, x, x},
    {x, x, x, x},
    {x, x, x, x},
    {x, x, x, x}
}

완전탐색을 통해 DP[i][j] != false 일때 (더 훔치러 갈 수 있다는 뜻)

만약 다음과 같은 조건에 맞다면

  • i + a_trace < n
  • j + b_trace < m

info[0] = {1,2}

  • A가 훔치면 DP[1][0] = true
  • B가 훔치면 DP[0][2] = true



가능한 지점은 DP[1][0], DP[0][2] 로 총 2가지가 된다

이어서 1번째 idx를 보며 마찬가지로 조건에 맞는지 확인

  • info[1] = {2,3}
  • i + a_trace < n
  • j + b_trace < m

A가 훔치면

  • DP[3][0] = true
  • DP[2][2] = true

B가 훔치면

  • DP[1][3] = true



이어서 2번째 idx를 보자 info[2] = {2,1}

가능한 지점은 DP[3][0], DP[2][2], DP[1][3]

A가 훔치면

  • DP[3][3] = true

B가 훔치면

  • DP[3][1] = true
  • DP[2][3] = true



이제 훔치는건 전부 종료되었다.

지금부터 우리가 구해야할 것은 A의 최소 누적흔적이니 완전탐색을 통해 true인 것중 i만 반환하고 return하면 되겠다. 그렇다면 위 testcase 기준으로 DP[2][3]2를 반환할 것이다.

그 외는 -1을 return한다.

여기까지의 연산시간은 다음과 같겠다.

O(l * n * m) = info 길이 * n * m

✅ 코드 (bruteforce)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Solution {
    public int solution(int[][] info, int n, int m) {
        
        // 1. 2차원 배열 생성
        boolean[][] DP = new boolean[n][m];
        
        // 2. 초기값, 처음에 A,B 누구도 어떤 것도 훔치지 않았다
        DP[0][0] = true;
        
        // 3. 검사
        for (int[] cur : info) {
            int a_trace = cur[0];
            int b_trace = cur[1];
            
            // 3-1. 새로운 배열
            boolean[][] next_DP = new boolean[n][m];
            
            // 3-2. 완전탐색
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    // 3-3. 방문 안했으면 skip (도둑질한 현황이 없다는 뜻)
                    if (!DP[i][j]) continue;
                    
                    // 3-4. A 혹은 B가 도둑질한 현황이 있다면
                    
                    // 3-5. A의 흔적을 더 남길 수 있다면
                    if (i + a_trace < n) {
                        next_DP[i + a_trace][j] = true;
                    }
                    
                    // 3-6. B의 흔적을 더 남길 수 있다면
                    if (j + b_trace < m) {
                        next_DP[i][j + b_trace] = true;
                    }
                }
            }
            // 4. 갱신
            DP = next_DP;
        }
        
        
        // 5. A의 누적 최소흔적 찾기
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (DP[i][j]) {
                    return i;
                }
            }
        }
        
        // 6. A의 누적최소흔적이 없다면
        return -1;
    }
}




그러면 다른 방법인 1차원으로 줄인 DP도 가능하지 않을까?

  • B의 흔적이 x 일때 최소 A의 흔적

DP 접근법

B를 고른 갯수 기준으로 각 A의 누적흔적 최소를 만들자

그래서 배열의 크기는

1
int[] DP = new int[m];

그리고 INF로 전부 초기화하고 맨처음값은 0을 넣자

그리고 상태 갱신을 위해 매 info 반복마다 next_DP 를 만들며 마찬가지로 INF로 전부 초기화하자

testcase

1
2
3
4
5
info = { {1,2}, {2,3}, {2,1} }
n = 4;
m = 4;

result = 2

여기서 INF를 활용해 DP[i] == INF 라면 훔치는게 이어지지 못하는거니 skip

info idx -> 0일때

1
2
3
4
5
6
7
8
9
10
DP = {0, INF, INF, INF};
next_DP = {INF, INF, INF, INF}

->

next_DP = {1, INF, 0, INF}

-> 

DP = {1, INF, 0, INF}
  • B = 0일때 A = 1
  • B = 2일때 A = 0


info idx -> 1일때

1
2
3
4
5
6
DP = {1, INF, 0, INF}
next_DP = {INF, INF, INF, INF}

->

next_DP = {3, INF, 2, 1}
  • B = 0일때 A += 2
  • B = 2일때 A += 2
  • B = 3 일때 A = 1 (idx 1일때 B도둑이 흔적을 3남겼다면 당연히 최소값으로는 idx 0일때 A도둑이 흔적 1을 남긴것)


info idx -> 2일때

1
2
3
4
5
6
DP = {3, INF, 2, 1}
next_DP = {INF, INF, INF, INF}

->

next_DP = {INF, 3, INF, 2}

j = 0일때 info[2][1] 고르면 B는 1개 훔친거니 A는 당연히 3개가 되야한다

이후 j = 1일때 INF라서 skip

j = 2일때 info[2][1] 고르면 B는 3개 훔치는게 되고, 그때 A의 최솟값은 2

j = 3일때 DP[3] = 1, info[2][1] = 2 이것들을 더하면 3 -> 즉 더크니 skip

최종

answer = INF 를 생성하고

1중 for문을 통해 DP[i] 값과 비교하며 마지막으로 answer == INF 라면 return -1

그게 아니라면 return answer



✅ 코드 (DP)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Solution {
    static int INF = Integer.MAX_VALUE;
    
    
    public int solution(int[][] info, int n, int m) {
        
        // 1. B기준으로 각 A의 누적흔적 최소
        int[] DP = new int[m];
        
        // 2. INF 로 초기화하고, 첫값은 0
        for (int i = 0; i < m; i++) {
            DP[i] = INF;
        }
        DP[0] = 0;
        
        // 3. 검사
        for (int[] cur : info) {
            int a_trace = cur[0];
            int b_trace = cur[1];
            
            // 3-1. 다음 DP 생성하고, INF 초기화
            int[] next_DP = new int[m];
            for (int i = 0; i < m; i++) {
                next_DP[i] = INF;
            }
            
            
            // 3-2. 최소값 갱신
            for (int i = 0; i < m; i++) {
                // 3-1. 만약 훔치는게 이어지지 못하면 skip
                if (DP[i] == INF) continue;
                
                // 3-2. 그게 아니라면
                
                // 3-3. a흔적을 더 남길 수 있다면
                if (DP[i] + a_trace < n) {
                    next_DP[i] = Math.min(next_DP[i], DP[i] + a_trace);
                }
                
                // 3-4. b흔적을 더 남길 수 있다면
                if (i + b_trace < m) {
                    next_DP[i + b_trace] = Math.min(next_DP[i + b_trace], DP[i]);
                }
            }
            
            // 4. 갱신
            DP = next_DP;
        }
        
        // 5-1. 출력 (음수처리)
        int answer = INF;
        for (int i = 0; i < m; i++) {
            answer = Math.min(DP[i], answer);
        }
        if (answer == INF) return -1;
        
        // 5-2. 출력 (음수가 아닐때)
        return answer;
    }
}
This post is licensed under CC BY 4.0 by the author.

Trending Tags