알고리즘/알고리즘 문제풀이
dijkstra_Leetcode_787. Cheapest Flights Within K Stops [swift]
Downey
2025. 2. 24. 17:45
https://leetcode.com/problems/cheapest-flights-within-k-stops/description/
요구사항
- 이동가능한 간선을 주고, 시작점과 도착점을 준다. 이 때, k번 이하로 이동하는 경로를 사용해야한다.
- 가능한 경로가 없을 때, -1을 반환한다.
- 가장 작은 cost를 반환한다.
문제접근
- 매개변수 flights로 graph 생성, djikstra 시작 준비.
- 정답을 저장할 costs, queue를 선언하고 매개변수 src를 기준하여 초기값 주입
- djikstra 시작.
- queue의 첫번째 element 추출
- graph를 참조하여, nextFlight를 순회한다.
- if costs[nextDst].isEmpty 라면, 새로운 정보를 costs[nextDst]와 queue에 추가한다.
- else 일 때 costs[nextDst]에서, nextTransfer와 동일한 값을 가진 index 찾는다.
- 있다면, 해당 index의 정보를 cost로 비교하여 더 효율적인 값으로 대체한다.
- 없다면, 새로운 환승 정보를 가진 다른 경우이므로, 새로운 element를 추가한다.
와중에 발생한 디버깅
- 처음에는 with at most를 잘 못 해석하여, 정확한 k를 요구한다고 생각했다. 근데 k 이하의 환승이 요구사항이었다.
- 디버그 중 circling이 일어나는 것을 눈치챘다. ( time out이 발생해서 IDE에서 빌드해서 알게 됨 )
- 첫 번째 testCase를 확인 할 경우, 0 -> 1 -> 2 -> 0 으로 circling하는 것을 확인 할 수 있다.
- 떄문에, 겹치는 환승 횟수가 없을 때, 새로운 환승 횟수와 요금이 더 비효율이라면 해당 경우를 무시한다. 이를 통하여 circling을 제거한다.
- transfer는 반복마다 올라간다. 그런데, 해당 문제에서 0->1->2로 이동시 환승을 1회로 간주한다.
- 때문에 fliter처리 시, transfer - 1을 수행했다.
아쉬운 나의 정답 코드
더보기
class Solution {
struct FlightInformation {
var cost: Int
var transfer: Int
}
func findCheapestPrice(_ n: Int, _ flights: [[Int]], _ src: Int, _ dst: Int, _ k: Int) -> Int {
var costs = [[FlightInformation]](repeating: [], count: n) // 목적지에 도착하는 여러가지 경우의 수를 고려한다.
var graph = [Int: [(Int, Int)]]() // [src, [(dst, cost)]]
var queue = [(Int, FlightInformation)]() // current
for flight in flights {
if graph[flight[0]] == nil {
graph[flight[0]] = []
}
graph[flight[0]]?.append((flight[1], flight[2]))
}
// 시작점을 초기화
let srcStart = FlightInformation(cost: 0, transfer: 0)
costs[src] = [srcStart]
queue.append((src, srcStart))
// 우선순위 queue에 시작점 추가, 어라 우선순위 queue여야하는가? 가능한 모든 이동 가능한 경로를 고려해야 하므로 아닌것 같은데?
// dijkstra 시작. ->
while !queue.isEmpty {
let current = queue.removeFirst()
let (currentStr, currentFlight) = current
let nextTransfer = currentFlight.transfer + 1
for nextFlight in graph[currentStr, default: []] { // graph: [src, [(dst, cost)]] nextFligt: (dst, cost)
let nextDst = nextFlight.0
// if nextDst == src { continue } // nextDst가 초기 위치로 돌아가는 경우가 있다. 이 코드를 주석처리하면 전체 성능이 더 나빠짐.
let price = nextFlight.1
if costs[nextDst].isEmpty {
let nextValue = FlightInformation(cost: price + currentFlight.cost, transfer: nextTransfer)
costs[nextDst].append(nextValue)
queue.append((nextDst, nextValue))
queue.sort(by: {
$0.1.cost < $1.1.cost
})
} else {
if var targetIndex = costs[nextDst].firstIndex(where: {
$0.transfer == nextTransfer
}) {
if costs[nextDst][targetIndex].cost <= currentFlight.cost + price { continue }
let nextCost = costs[nextDst][targetIndex].cost < currentFlight.cost + price
? costs[nextDst][targetIndex].cost
: currentFlight.cost + price
let nextValue = FlightInformation(cost: nextCost, transfer: nextTransfer)
costs[nextDst][targetIndex] = nextValue
queue.append((nextDst, nextValue))
queue.sort(by: {
$0.1.cost < $1.1.cost
})
} else {
let maxValue = costs[nextDst].max(by: {$0.cost < $1.cost})!
// 초기위치가 아니라 도중으로 돌아가서 circling하는 경우도 있는 것 같다.
if price + currentFlight.cost >= maxValue.cost // circling에 대한 예외처리.
&& nextTransfer >= maxValue.transfer {
continue
}
let nextValue = FlightInformation(cost: price + currentFlight.cost, transfer: nextTransfer)
costs[nextDst].append(nextValue)
queue.append((nextDst, nextValue))
queue.sort(by: {
$0.1.cost < $1.1.cost
})
}
}
}
}
let dstCosts = costs[dst]
let filtered = dstCosts.filter { $0.transfer - 1 <= k }
// costs에서 목적지 index의 정보를 추출. filter로 적합k(k 이하 경유)값을 가진 element 추출. isEmpty? -1 : min()
return filtered.isEmpty ? -1 : filtered.min(by: { $0.cost < $1.cost })!.cost
}
}
해당 코드는 효율성에서 나쁜 결과를 얻었다.
그래서 더 좋은 코드를 찾는다...
정답코드
화가난다... 단순반복문에 가까운 코드...
“K Stops 제한”이 있는 경우, 단순 BFS가 더 적합
• 다익스트라는 한 번 방문한 노드의 거리를 갱신하면 더 이상 방문하지 않음
• 하지만 이 문제에서는 같은 노드를 방문하더라도 K개 이하의 경유 조건을 만족해야 하므로, 여러 번 방문이 필요할 수 있음
• 따라서 “비용이 최소인 경로”보다 “경유 횟수(K)를 제한하는 BFS”가 더 적합
💡 즉, 다익스트라는 출발 → 목적지까지 가는 최단 경로를 보장하지만,
이 문제는 “경유 횟수 제한이 있으므로 비용이 적은 경로라도 탐색을 중단하면 안 된다.”
라고 GPT가 답변했다...
아래 코드는 k번 반복을 수행함으로써, 내 코드에서 관리했던 transfer정보가 불필요하다.
더보기
class Solution {
func findCheapestPrice(_ n: Int, _ flights: [[Int]], _ src: Int, _ dst: Int, _ k: Int) -> Int {
var prices = Array(repeating: Int.max, count: n)
prices[src] = 0
for i in 0..<k+1{
var temp = prices
for flight in flights{
let start = flight[0]
let end = flight[1]
let p = flight[2]
if prices[start] != Int.max{
temp[end] = min(temp[end], p + prices[start])
}
}
prices = temp
}
let value = prices[dst]
return value != Int.max ? value : -1
}
}