787. Cheapest Flights Within K Stops - cocoder39/coco39_LC GitHub Wiki

Option 1: Dijkstra Unlike traditional Dijkstra where cost is the only factor being considered when deciding priority, we should also allow a node to be added to queue if it can reduce #stop (even thought it may cost more than if we take another route which also passes through this node).

Time complexity: T = O(E * log E)

  • O(E) to build graph
  • we perform heap push for each edge O(E * log E)

Space complexity: O(E + V) to build graph and O(E) for queue

class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        graph = collections.defaultdict(list)
        for f, t, p in flights:
            graph[f].append((t, p))
        
        heap = [(0, -1, src)] 
        cost = [float("inf")] * n
        steps = [float("inf")] * n
        while heap:
            totalPrice, stopCount, curNode = heapq.heappop(heap)
            
            if stopCount <= k and curNode == dst:
                    return totalPrice
                
            if stopCount >= k:
                continue
                
            cost[curNode] = totalPrice
            steps[curNode] = stopCount
                
            for neighbor, price in graph[curNode]:
                if cost[neighbor] > totalPrice + price:
                    cost[neighbor] = totalPrice + price
                    heapq.heappush(heap, (totalPrice+price, stopCount+1, neighbor))
                elif steps[neighbor] > stopCount + 1:
                    steps[neighbor] = stopCount + 1
                    heapq.heappush(heap, (totalPrice+price, stopCount+1, neighbor))
                    
        return -1

Option 2: recursion + memo

Time complexity: O(V * k) as there are V * k states in memo space complexity: O(V + E) for graph building, O (V * K) for memo, O(V * K) for recursion stack since recursion stack is not deeper than total state count V*k

class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        graph = collections.defaultdict(list)
        for f, t, p in flights:
            graph[f].append((t, p))
        
        def helper(node, stop):
            if node in memo and stop in memo[node]:
                return memo[node][stop]
            
            if node == dst:
                return 0
            
            if stop < 0:
                return float("inf")
            
            res = float("inf")
            for nextNode, cost in graph[node]:
                res = min(res, cost + helper(nextNode, stop-1))
            memo[node][stop] = res
            return res
        
        memo = collections.defaultdict(dict)
        helper(src, k)
        return memo[src][k] if memo[src][k] != float("inf") else -1

Option 3: bfs

performing k levels of bfs, meanwhile updating the distance for each node within the search.

Time complexity: k times while loop, each loop iterates M edges and M < E so O(E * k) is an upper bound

space complexity: O(E + V) to build graph, O(V*K) to build distances dict. The size of queue is hard to determine, a node may be at same level of different paths so len(queue) can be greater than V. O(E * K) is a good upper bound considering executing while loop K times and each time won't iterate all E edges (each time iterate edges for nodes at stops level)

class Solution:
    
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int:
        
        graph = collections.defaultdict(list)
        for f, t, p in flights:
            graph[f].append((t, p))
            
        distances = {(src, 0): 0}
        queue = deque([src])
        stops = 0
        ans = float("inf")
        
        while queue and stops < K + 1:
            
            for _ in range(len(queue)):
                curNode = queue.popleft()
                for nextNode, cost in graph[curNode]:
                    if stops == K and nextNode != dst:
                        continue
                        
                    dU = distances.get((curNode, stops), float("inf"))
                    dV = distances.get((nextNode, stops + 1), float("inf"))
                    if dU + cost < dV:
                        distances[nextNode, stops + 1] = dU + cost
                        queue.append(nextNode)
                            
                        if nextNode == dst:
                            ans = min(ans, dU + cost)
            stops += 1   
        
        return -1 if ans == float("inf") else ans

Option 4: bellman-ford

original bellman-ford 对所有的边进行n-1轮松弛操作,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1边. To get shortest path with k+2 nodes we need to iterate k+1 times

Time complexity is O(EK) and space is O(V)

attention: temp[t] = min(temp[t], distances[f] + p) instead of distances[t] = min(distances[t], distances[f] + p) or temp[t] = min(temp[t], temp[f] + p)

class Solution:
    
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int:
        distances = [float('inf')] * n
        distances[src] = 0
        
        for i in range(min(K+1, n-1)):
            temp = distances[:]
            for f, t, p in flights:
                temp[t] = min(temp[t], distances[f] + p)
            distances = temp[:]
                    
        return -1 if distances[dst] == float("inf") else distances[dst]