Home (프로그래머스 | Kotlin) - 2차원 동전 뒤집기
Post
Cancel

(프로그래머스 | Kotlin) - 2차원 동전 뒤집기

해결 방법

이 문제는 특정 행, 혹은 열을 뒤집어서 원하는 형태를 만드는 게 핵심이다. 어떤 행, 혹은 열을 뒤집는 순서가 중요한 것이 아니다. 어떤 행, 혹은 열의 상태를 어떻게 할 것인지(뒤집거나 그대로 두거나)가 중요하다. M X N 형태에서 특정 열과 행의 상태를 결정하는 방식으로 로직을 짜면 된다.

뒷면, 앞면의 상태값을 비트로 표현해보자

문제 마지막에 보면 0 은 동전의 앞면, 1 은 동전의 뒷면을 의미한다. 그렇다면 0 1 을 비트 연산으로 처리해보자. 예를 들어서 2 X 2 형태라고 가정해보자. 행과 열은 각각 2개고 다음과 같이 표현할 수 있다.

두 개면 모두 앞면인 경우와 모두 뒷면 경우, 한쪽은 앞면 한쪽은 뒷면인 경우가 존재한다. 이를 비트 형식으로 표현하면 다음과 같이 표현할 수 있다. 예를 들어서 행에서 00 은 첫 번째 행, 두 번째 행 모두 앞면인 상태를 의미한다.

1
2
행 ->00 01 10 11 
열 -> 00 01 10 11

시프트 연산을 응용하여 모든 경우의 수(완전 탐색)를 꺼내 처리해보자

비트 00 은 십진수로 0 을 의미한다. 비트 011 이다. 비트 102 이다. 비트 113 이다. 즉, 0 부터 3 까지 반복문을 통해서 나올 수 있는 동전판의 모든 경우를 꺼내서 타겟과 일치하는지 검사할 수 있다.

1
2
for(i in 0 until (1 shl 2))
// 1 shl 2 => 2^2를 의미한다.

여기서 행과 열의 비트를 합쳐서 하나의 반복문으로 처리한다. 이렇게 하면 2X2 형태에서 나올 수 있는 경우의 수를 하나의 비트식으로 표현할 수 있다.

예를 들어서 2X2 형태에서 모든 행은 뒷면이고 모든 열은 앞면인 경우라면 1100, 반대로 모든 행은 앞면이고 모든 열이 뒷면이라면 0011 이다. 맨 앞의 두 개가 열의 상태값이고 뒤의 두 개가 행의 상태값이다.

이렇게 0000 부터 1111 까지 모든 조합을 꺼내서 원본과 비교하여 뒤집거나 그대로 두는 코드를 작성하면 된다.

1
2
3
4
5
6
for( i in 0 until (1 shl col+row))
// col => 행의 크기
// row => 열의 크기
// 2X2형태라면 (1 shl 4) => 2^4가 된다.

// 0000 ~ 1111 까지 루프

비트 연산을 이용하여 처리하자

1
2
for( i in 0 until (1 shl col+row))
// 2X2크기라고 가정

해당 반복문을 동작시키면 0000 부터 1111 까지 나오는데 현재 1010 차례라고 가정해보자. 그리고 원본이 1100 라고 하자. 앞의 두 개는 열의 상태값을 의미하고 뒤의 두 개는 행의 상태값을 의미한다.

행부터 살펴보면 2^1 부분의 값이 다르다. 반면에 2^0 부분은 서로 0 이기 때문에 뒤집을 필요가 없다. 따라서 값이 다른 2^1 부분은 뒤집어야 함을 의미하므로 이 부분은 뒤집어주면 된다.

첫 번째 행의 인덱스는 0, 두 번째 행의 인덱스는 1 이다. 첫 번째 행의 상태값을 의미하는 비트 위치는 2^0 , 두 번째 행은 2^1 이다. 따라서 두 번째 행을 뒤집으면 된다. 이 개념을 코드로 옮기면 다음과 같다.

XOR 비트 연산을 사용하면 뒤집는 동작을 쉽게 처리할 수 있다.

행을 처리하는 코드

1
2
3
4
5
6
for(c in 0 until col) {
    if(b and (1 shl c) != 0) {
        flipCounter++
        tmp[c] = tmp[c].map { it xor 1 }.toIntArray()
    }
}

열을 처리하는 코드

1
2
3
4
5
6
for(r in 0 until row) {
    if(b and (1 shl r + col) != 0) {
        flipCounter++
        for(i in 0..tmp.lastIndex) { tmp[i][r] = tmp[i][r] xor 1 }
    }
}

열의 경우에는 2^2 부터 검사를 해야 하기 때문에 (1 shl r+col)AND 처리를 해줘야 한다.

마지막은 target 과 비교하여 동일한지 검사한다.

1
if(tmp.contentDeepEquals(target)) { answer = min(answer, flipCounter) }

전체 코드

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
import kotlin.math.*

class Solution {
    fun solution(beginning: Array<IntArray>, target: Array<IntArray>): Int {
        val col = beginning.size
        val row = beginning[0].size
        var answer = Int.MAX_VALUE

        for(b in 0 until (1 shl col+row)) {
            val tmp = Array(col) { i -> IntArray(row) { j -> beginning[i][j] } }
            var flipCounter = 0

            for(c in 0 until col) {
                if(b and (1 shl c) != 0) {
                    flipCounter++
                    tmp[c] = tmp[c].map { it xor 1 }.toIntArray()
                }
            }
            for(r in 0 until row) {
                if(b and (1 shl r + col) != 0) {
                    flipCounter++
                    for(i in 0..tmp.lastIndex) { tmp[i][r] = tmp[i][r] xor 1 }
                }
            }
            if(tmp.contentDeepEquals(target)) { answer = min(answer, flipCounter) }
        }
        return if(answer == Int.MAX_VALUE) -1 else answer
    }
    
}

(프로그래머스 | C++) - 방금그곡

(프로그래머스 | Kotlin) - 메뉴 리뉴얼