N * M 행렬로 표현되는 맵이 있다. 0은 이동할 수 있는 길이고 1은 이동할 수 없는 벽이며 상하좌우로 이동할 수 있다. (1, 1)에서 (N, M)까지 최단거리로 이동해야한다. 이동 거리는 시작하는 칸과 끝나는 칸도 포함되어야하며, 이동 중 벽을 한 개 까지 부수고 이동할 수 있다.
알고리즘
맵을 입력받는다. (시간초과 주의)
3차원의 visited 배열을 선언한다.
bfs를 수행한다.
접근방법
시간초과 때문에 삽질 어어어어어어어엄청 한 문제이다.
첫 번째 접근 - (안 될 거 뻔하지만, 무식하게 풀어보자)
지도를 입력받을 때, walls라는 벽의 위치를 모두 입력받은 후, 모든 경우의 수를 탐색하는 방법이다.
N, M의 범위가 최대 1,000이므로 O(N2) -> 1,000,000,000,000을 훌쩍.. 넘는다!
너무 당연히 시간초과
N 번째 접근 - 3차원의 visited 배열을 사용한다. (다른 사람들의 아이디어를 참고했다.. 다섯시간 고민함 ㅎ)
visited[n][m][2]를 통해서 이제까지 벽을 부순 적 있음?을 확인하는게 핵심 아이디어다.
Swift는 3차원 배열 아이디어만으로 이 문제를 풀 수 없다. 시간초과가 발생하기 때문이다. 우리는 자료구조에 더 신경을 써줘야된다.
창고에 토마토가 보관되어있다. 보관 후 하루가 지나면, 익지 않은 토마토는 인접해있는 익은토마토의 영향을 받아 하루가 지나면 익게 된다. (토마토가 혼자 저절로 익는 경우는 없다) 토마토가 며칠이 지나 다 익게 되는지, 최소 일 수를 구해라. (1은 익은 토마토, 0은 익지 않은 토마토, -1은 토마토가 없는 칸이다)
알고리즘
주어진 토마토 정보를 입력받는다.
이때, 토마토가 입력 된 경우, tomato라는 배열에 넣는다.
입력받은 토마토 큐를 이용하여 bfs를 수행한다.
토마토 상자에 0이 있는 경우만 익는다고 생각하면 된다.(이동한다)
결과를 출력한다.
토마토 상자를 조회하며 0이 하나라도 있다면 -1을 출력한다.
토마토 상자의 최댓값을 출력한다.
접근방법
처음에는 모든 시작 토마토에 대해 각각 bfs를 수행해야한다고 생각했다.
하지만 시작 queue에 모든 토마토를 넣고 실행해도 depth별로 실행되기 때문에 이 문제를 해결할 수 있다.
즉 bfs 한 번만으로 문제를 해결할 수 있음을 의미한다.
이때 queue에 기존 bfs처럼 (0,0)이 아니라 모든 토마토 배열이 들어간다면 가능하다!
시간초과 해결 방법
우선 Swift에서 queue는 매우 쉽게 구현할 순 있다. (removeFirst를 지원하니까)
하지만 RemoveFirst는 시간복잡도 O(n)이 소요된다!!
여러가지 해결 방법이 있다.
배열의 인덱스를 이용하여 pop 된 것 처럼 사용한다. (내가 채택한 방법)
reverse() -> removeFirtst() -> reverse()를 사용한다. (reverse는 O(1)이다)
실제 큐 구현하기 등 여러가지가 있다.
코드
let input =readLine()!.split { $0 ==" " }.map { Int($0)! }, (m, n) = (input[0], input[1])var map = [[Int]]()var tomato = [(Int, Int)]()// 정보를 입력받는다. 단, 토마토인 경우 따로 배열에 저장한다.for y in0..< n {let input =readLine()!.split { $0 ==" " }.map { Int($0)! }for x in0..< m {if input[x]==1 { tomato.append((y, x)) } } map.append(input)}bfs()print(getResult())funcbfs() {let dy = [-1, 0, 1, 0]let dx = [0, 1, 0, -1]var index =0// 토마토 배열을 이용하여 bfs를 수행한다.while tomato.count> index {let(y, x)= tomato[index] index +=1for i in0..<4 {let ny = dy[i]+ ylet nx = dx[i]+ xguard0..<n ~= ny &&0..<m ~= nx && map[ny][nx] ==0else { continue } map[ny][nx] = map[y][x] +1 tomato.append((ny, nx)) } }}funcgetResult() ->Int {var result =0for t in map {if t.contains(0) { return-1 } result =max(result, t.max()!) }return result -1}
N*N 공간에 물고기 M마리와 아기 상어 1마리가 있다. 아기상어는 상, 하, 좌, 우로 움직일 수 있다. 이때, 자기보다 몸집이 큰 물고기는 지나갈 수 없고, 몸집이 같은 물고기는 지나갈 수 있다. 물고기가 자기보다 몸집이 작다면, 먹을 수 있는데 자기 크기 만큼 물고기를 먹으면 1만큼 아기 상어의 몸집이 커진다. 이때, 상어가 몇 초 동안 엄마에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는지 계산해라.
아기상어는 더 이상 먹을 수 있는 물고기가 공간에 없다면, 엄마상어에게 도움을 요청한다.
먹을 수 있는 물고기가 1마리라면, 그 물고기를 먹으러 간다.
먹을 수 있는 물고기가 1마리보다 많다면, 거리가 가장 가까운 물고기를 먹으러 간다.
거리는 아기 상어가 지나가야하는 칸의 최소 개수이다.
거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 많다면 가장 왼쪽에 있는 물고기를 먹는다.
알고리즘
n을 입력받는다.
지도 정보를 입력받는다.
입력 값에 9가 있다면 상어의 위치를 저장하고, 해당 map을 0으로 초기화한다.
입력 값이 물고기라면, 물고기 집합에 정보를 추가한다.
bfs가 0을 리턴할 때까지 bfs를 수행한다.
물고기 집합이 비어있다면 0을 리턴한다. (종료조건 1)
flood fill 알고리즘을 수행하여 depth 별로 너비 우선 탐색을 수행한다.
다음 위치가 물고기라면 임시 물고기 배열에 저장하고, 아니라면 계속 탐색을 수행한다.
같은 depth의 탐색이 끝나면 임시 물고기 배열에 물고기가 있는지 확인한다. (없다면 계속 bfs 수행)
만약 임시 물고기 배열에 물고기가 있다면 조건에 맞는 물고기를 먹으러 가야한다.
임시 물고기 배열을 정렬한다. (좌상단 물고기 찾기)
전역에 있는 물고기 집합에서 먹을 물고기를 제외한다.
이제 물고기가 없으므로 해당 위치를 0으로 초기화한다.
먹은 물고기를 +1 해준다. (상어 커지는 조건)
map을 모두 조회했다면 0을 리턴한다. (종료조건 2)
접근 방법
고민해야하는 부분
dy, dx의 방향만으로 조건을 성립할 수 없다! -> flood fill 사용 (매우 중요)
무조건 같은 depth에서 먹을 수 있는 물고기를 확인한 후 정렬을 수행해줘야한다. (백준질문)
물고기를 먹을 수 없는 상황
남은 물고기들이 전부 다 크기가 상어보다 크거나 같은 경우
나보다 작은 물고기가 있지만, 큰 물고기들이 막고 있어서 먹지 못하는 경우
이 경우가 굉장히 중요하다.
return은 물고기를 먹은 상황에서만 해줘야된다. (이동한 후에 물고기를 먹지 못한 상황에서 depth를 리턴시키면 틀린 답이 나온다)
상어 크기 정의 방법
Swift의 didSet을 사용하면 깔끔하게 쓸 수 있다!
코드
// 물고기 자료형structFish:Hashable{let y: Intlet x: Intvar size: Int}let n =Int(readLine()!)!var map = [[Int]]()var sharkX =0, sharkY =0var fishes =Set([Fish]())var sharkSize =2var ateFish =0 { // 상어 크기 정의didSet {if ateFish == sharkSize { sharkSize +=1 ateFish =0 } }}// 주어진 정보를 입력받는다.// 상어의 경우, 위치만 저장하고, map에는 0을 저장해준다.// 물고기의 경우 집합에 넣어주자.for y in0..< n {var input =readLine()!.split { $0 ==" " }.map { Int($0)! }for x in0..< n {let size = input[x]switch size {case0:breakcase9: (sharkY, sharkX) = (y, x) input[x]=0default: fishes.insert(Fish(y: y, x: x, size: size)) } } map.append(input)}var result =0whiletrue {let count =bfs(sharkY, sharkX) result += countif count ==0 {print(result)break }}funcbfs(_y: Int, _x: Int) ->Int {let dy = [-1, 0, 1, 0]let dx = [0, 1, 0, -1] var visited = [[Bool]](repeating: [Bool](repeating:false, count: n), count: n) visited[y][x] =truevar queue = [(y, x)]var count =0// 이동 거리var fish = [Fish]()// 현재 depth에서 먹을 수 있는 물고기 집합if fishes.isEmpty { return0 } // 먹을 수 있는 물고기가 없다면 0을 리턴 (종료조건1)while!queue.isEmpty { count +=1let depth = queue.count// flood fillfor_in0..< depth {let(y, x)= queue.removeFirst()for i in0..<4 {let ny = dy[i]+ ylet nx = dx[i]+ xguard0..< n ~= ny &&0..< n ~= nx else { continue }guard!visited[ny][nx] && map[ny][nx] <= sharkSize else { continue } visited[ny][nx] =truelet next = map[ny][nx]if[0, sharkSize].contains(next) { // 이동하는 경우 queue.append((ny, nx)) } else { // 물고기를 먹을 수 있는 경우 fish.append(Fish(y: ny, x: nx, size: next)) } } }// 이번 depth에서 물고기를 먹을 수 있다면if!fish.isEmpty {// 조건에 맞는 물고기를 찾는다. fish = fish.sorted { $0.x < $1.x }.sorted { $0.y < $1.y }let shark = fish[0]// 물고기 배열에서 해당 물고기를 지운다. fishes.remove(shark)// 상어의 위치를 업데이트한다. (sharkY, sharkX) = (shark.y, shark.x)// 지도에 0으로 저장한다. map[sharkY][sharkX] =0// 물고기 수를 추가한다. ateFish +=1// 이제까지 이동한 거리를 리턴한다.return count } }// 모든 지도를 탐색했음에도 물고기를 먹지 못했다(종료조건2)return0}
잘못된 접근 1 - 크기가 큰 물고기도 지나갈 수 있다고 생각했다 (문제를 잘 읽자)
문제를 잘 읽어야하는 이유다.
상어보다 몸집이 큰 경우도 움직일 수 있다고 생각해서 단순 distance를 구해서 문제를 풀었었다...
// 물고기 구조체structFish:Hashable{var y: Intvar x: Intvar size: Intinit(_y: Int, _x: Int, _size: Int) { self.y = y self.x = x self.size= size }}let dy = [-1, 0, 1, 0]let dx = [0, 1, 0, -1]let n =Int(readLine()!)!var fishes =Set([Fish]())var shark =Fish(0, 0, 0)var(count, result)= (0, 0)// 물고기 정보 입력받기for y in0..< n {let input =readLine()!.split { $0 ==" " }.map { Int(String($0))! }for x in0..< n {if input[x]==9 { shark =Fish(y, x, 2) } elseif input[x]!=0 { fishes.insert(Fish(y, x, input[x])) } }}whiletrue {if count == shark.size { count =0 shark.size+=1 }// 조건에 따라 정렬var foodFish =Array(fishes).filter { $0.size< shark.size } .sorted { $0.x < $1.x } .sorted { $0.y < $1.y } .sorted {calculateDistance($0, shark)<calculateDistance($1, shark) }guard!foodFish.isEmptyelse { break }let nextFish = foodFish.removeFirst() fishes.remove(nextFish) count +=1 result +=calculateDistance(shark, nextFish) (shark.y, shark.x) = (nextFish.y, nextFish.x) }print(result)// 두 물고기 사이의 거리 계산funccalculateDistance(_shark: Fish, _fish: Fish) ->Int {abs(shark.x - fish.x)+abs(shark.y + fish.y)}