LeetCode발행일 2025. 1. 27.원본 https://blog.naver.com/jword_/223740028247 ↗

이차원 배열 이동 문제(자료구조 사용 연습)

이차원 배열 이동 문제(자료구조 사용 연습) — #프로그래머스 #충돌위험찾기 #개발자의도구들 #이차원배열이동 사용된 언어: 코틀린, 혹은 파이썬 순서: ...

#LeetCode#Naver Blog

#프로그래머스 #충돌위험찾기 #개발자의도구들 #이차원배열이동

​

​

  • 사용된 언어: 코틀린, 혹은 파이썬
  • 순서: 로직, 코드 구현, 코드 분석

​

아이디어 정리

프로그래머스 \[PCCP 기출문제\] 3번 / 충돌위험 찾기

text 코드 예제
                                    1. 각 로봇은 1초에 한칸씩 이동한다
-- 로봇은 좌표차리를 이용하여 r or c로 += 1 씩 이동한다.
---- r먼저 움직이고 c를 움직여야 한다. (우선순위 r > v)

2. r과 c는 x, y 좌표평면과는 다르다 -> 하지만 이를 크게 의식할 필요는 없다.

3. 매 초마다 모든 로봇이 한번씩 움직인다.
-- 모든 로봇이 goal을 했다면 운행을 종료시킨다. while문 + goal
-- 매번 모든 로봇의 경로를 확인 후 겹치는지 체크한다. - 좌표를 체크해보면 된다.
text 코드 예제
                                    💡 각 로봇에게 고유의 목표를 할당하기?
# n초에 벌어지는 일들
-- 1번째 로봇의 목표위치를 기억
-- 1번째 로봇의 현재위치를 확인
-- 다른 로봇과 겹치는 구간이 있는지 확인
---- 중복 겹침이 일어나지 않도록 로봇 쌍으로 확인
-- 이동 경로 결정
-- 이동
-- 2...n 번째 로봇 모두 반복

-- endCheck이 로봇 수 만큼 나오면 운행 종료
-- 겹치는 구간마다 return값 1씩 증가.

🤔 발생하는 의문점들
모든 로봇의 위치를 따라 관리해서(메모리) 매번 확인?
or
메모리를 안쓰고 확인하는 법?

1차시도

kotlin 코드 예제
                                    class Solution {
    fun solution(points: Array<IntArray>, routes: Array<IntArray>): Int {
        var answer: Int = 0
        var endCheck = 0
        var robotCount = routes.size

        // current position update
        var currentPositions: MutableList<Pair<Int,Int>> = mutableListOf()
        var eachGoals: MutableList<Pair<Int, Int>> = mutableListOf()
        for (route in routes) {
            val sR = points[route[0] - 1][0]
            val sC = points[route[0] - 1][1]
            val gR = points[route[route.size - 1] - 1][0]
            val gC = points[route[route.size - 1] - 1][1]
            currentPositions.add(Pair(sR, sC))
            eachGoals.add(Pair(gR, gC))
        }

        // move
        while (endCheck != robotCount) {
            // each time all Robots move!!
            var duplicated: MutableSet<Int> = mutableSetOf()
            for (i in 0 until currentPositions.size) {
                // i robot cur, goal
                var current = currentPositions[i]
                val goal = currentPositions[i]

                // check duplicated
                for (j in i until currentPositions.size) {
                    if (duplicated.contains(j)) break
                    if (current == currentPositions[j]) {
                        duplicated.add(i)
                        duplicated.add(j)
                        answer++
                    }
                }

                // decide moving (r first)
                var currentR = current.first
                var currentC = current.second
                val goalR = current.first
                val goalC = current.second

                // end check first
                if (currentR == goalR && currentC == goalC) {
                    endCheck++
                    break
                }

                if (currentR - goalR != 0) {
                    if (currentR - goalR > 0) {
                        // move to up
                        currentR--
                    }
                    currentR++
                } else if (currentC - goalC != 0) {
                    if (currentC - goalC > 0) {
                        currentC--
                    }
                    currentC++
                }
                // update position
                currentPositions.set(i, Pair(currentR, currentC))
             }
        }

        return answer
    }
}

// r > c 우선순위
// confilctions return
// 100 x 100
// 로봇의 수 = 2~100대
// 각 경로 2~100
// routes = 로봇 수
// routes[0] = [4, 2] -> 1번째 로봇은 points[4- 1] -> points[2 - 1]로 이동한다는 것을 의미.

// (r, c) -> 세로, 가로
// 0초에 충돌하는 것도 고려

//
text 코드 예제
                                    👉 발생한 오류들

1. route의 중간 경로 탐색 불가
-- route 내부에 목표경로가 여러개 존재하는데 이를 처리하지 못함

gR, gC -> 하나로 고정하면 안돼

2. 좌표 업데이트 미스
-- currenR 이후 바로 currentR을 감소시켜서 오류

3. 불안정적인 duplicated?
-->

2차 시도

text 코드 예제
                                    💡 다른 자료구조를 사용?

1. 동시간대 겹치는 로봇들을 확인하기 위해 좌표별 로봇의 갯수를 체크
-- 2개 이상인 경우 answer + 1
-- 이후 해시맵 초기화
-- key갑싈 Pair<Int, Int> (좌표)로 설정

2. route의 경우 모든 로봇에 대하여 동일한 크기를 가짐
-- 경로의 수가 일정하므로, for문을 통해 순차적으로 접근하도록 갱신

모든 로봇이 동시에 움직이도록 구현하려면 어떻게 해야하는지 고민

  • 모든 로봇에 대하여 현재위치 및 목표를 저장
  • 현재 위치 : currentPositions - \[Pair<Int, Int>, ...
  • 목표들 : goals - \[\[Pair<Int, Int>, ... \], ... \] 이런식으로 저장
  • 근데 이건 route의 1번 idx 부터
  • 그냥 point 좌표를 저장해도된다. intArray로

​

동작 정리

  1. 모든 currentPositions에 대하여
  • 현재 위치 좌표에 대한 robot count를 계산 - crush 체크(요구사항) ⭐
  • 다음 위치 확인 - route의 idx 1의 point 좌표를 계산
  • 현재위치 == 다음위치? 맞다면 idx 2의 좌표를 계산해야 해
  • 이동 -- R > C 우선순위로 이동 할 것
  • 1번을 반복한다.

​

🤔 route 경로 처리가 깔끔하게 되게 하려면 어떻게 해야하지??

  • 각 로봇별로 현재 목표를 저장해두기 - goals에

​

kotlin 코드 예제
                                    data class Goal(
    val r: Int,
    val c: Int,
    val idx: Int
)

class Solution {
    fun solution(points: Array<IntArray>, routes: Array<IntArray>): Int {
        var answer: Int = 0
        val robotCount = routes.size
        var routeSize = routes[0].size // for idx check

        // points = [[1, 2]. [1, 3] ... ] point size fixed
        // routes = [[1, 2, 3], [2, 3, 4]]... route -> route - 1 = point's idx
        var currents: MutableList<Pair<Int, Int>> = mutableListOf()
        var goals: MutableList<Goal> = mutableListOf()// [(r, c, idx)]
        var isFinished = BooleanArray(robotCount) {false} // finish check
        var finishCount = 0

        // initinal update
        for (i in 0 until robotCount) {
            val currPair = Pair(points[routes[i][0] - 1][0], points[routes[i][0] - 1][1])
            val initGoal = Goal(
                points[routes[i][1] - 1][0],
                points[routes[i][1] - 1][1],
                1
            )
            currents.add(currPair) // curr position
            goals.add(initGoal)
        }

        while(finishCount < robotCount) {
            var dupMap: MutableMap<Pair<Int, Int>, Int> = mutableMapOf()// {"(r, c)": count}
            for (i in 0 until robotCount) {
                // skip finished robot
                if (isFinished[i]) continue
                var (r, c) = currents[i] // now position
                var (gr, gc, idx) = goals[i] // now goal

                // count dup  - for start ...
                dupMap[Pair(r, c)] = (dupMap[Pair(r, c)] ?: 0) + 1

                // check is goal position?
                if (r == gr && c == gc) {
                    // check is last goal?
                    if (idx == routeSize -1) {
                        isFinished[i] = true
                        finishCount++
                        continue
                    }
                    // find next goal
                    val newGoalPoint = routes[i][idx + 1]
                    val newGoal = Goal(
                        r = points[newGoalPoint - 1][0],
                        c = points[newGoalPoint - 1][1],
                        idx = idx + 1
                    )
                    goals[i] = newGoal
                    gr = goals[i].r // new goal
                    gc = goals[i].c
                }

                // move
                val diffR = r - gr
                val diffC = c - gc

                if (diffR != 0) {
                    if (diffR > 0) {
                        currents[i] = Pair(--r, c)
                    } else {
                        currents[i] = Pair(++r, c)
                    }
                    continue
                } else if (diffC != 0) {
                    if (diffC > 0) {
                        currents[i] = Pair(r, --c)
                    } else {
                        currents[i] = Pair(r, ++c)
                    }
                }
            }

            // check crush (cur)
            for ((pair, cnt) in dupMap) {
                if (cnt >= 2) {
                    answer++
                }
            }
        }

        return answer
    }
}

// r > c 우선순위
// confilctions return
// 100 x 100
// 로봇의 수 = 2~100대
// 각 경로 2~100
// routes = 로봇 수
// routes[0] = [4, 2] -> 1번째 로봇은 points[4- 1] -> points[2 - 1]로 이동한다는 것을 의미.
text 코드 예제
                                    👉 추가된 로직
1. 현재위치가 goal에 해당한다면, 다음 gaol을 탐색 없다면 finished로 기록
2. 이를 위해서 Goal 클래스를 선언하고 idx를 넣어두었음.

복잡도 계산

시간복잡도

text 코드 예제
                                    시간 복잡도를 결정 하는 요소들
while, for

✅ while : 모든 경로를 이동할 때까지 반복
이는 routeSize에 해당함

✅ for : 모든 로봇이 한번씩 이동후 여러가지 조건을 체크
이는 robotSize에 해당

큰 틀에서는 routeSize * robotSize 만큼의 시간이 소요된다.

✏️ O(M * N)

공간복잡도

text 코드 예제
                                    결정 요소
map, list(currents, goals)

✅ list
robotSize만큼의 currents와 goals가 할당된다.
currents는 PairInt, Int>, goals는 Goal(Int, Int, Int)

Int = 4Byte로 가정 하면
list에서는 크게 robotSize * 20Byte의 메모리 할당  -> O(N)

✅ map
map은 robotSize만큼 매번 초기화 되므로 -> O(N)

✏️ O(N)

필요했던 지식들

text 코드 예제
                                    1. Kotlin의 set사용하기

setOf("name", "age")...
mutableSetOf("mutable!!")

​

문법 오류들

text 코드 예제
                                    1. mapCount

❌ dupMap[Pair(r, c)]?.let {
    dupMap[Pair(r, c)]++
}.else {           // <-- 이 부분이 Kotlin 문법에 없습니다.
    dupMap[Pair(r, c)] = 1
}
- 존재하지 않는 문법
✅  dupMap[Pair(r, c)]?.let {
    dupMap[Pair(r, c)]++
}?: run {dupMap[Pair(r, c)] = 1}

👉 이렇게 사용하기
dupMap[Pair(r, c)] = (dupMap[Pair(r, c)] ?: 0) + 1

후기

로직이 복잡하면 할 수록 문제를 단계별로 나눠서 실행하자. 각 기능을 하나의 method들로 보고 각 기능별로 구분해서 하나씩 천천히 구현하자. 여러개의 기능들이 요구되면 매우 정신이 없어서 머릿속에서 로직이 계속 꼬여버릴 수 있다. 하지만, 하나씩 천천히 해결하다보면 안정적인 코드 작성이 가능하다.