4

I am trying to solve LeetCode problem #787, "Cheapest Flights Within K Stops."

"There are n cities connected by some number of flights. You are given an array flights where flights[i] = [fromi, toi, pricei] indicates that there is a flight from city fromi to city toi with cost pricei. You are also given three integers src, dst, and k, return the cheapest price from src to dst with at most k stops. If there is no such route, return -1."

However, I am encountering an issue with a specific testcase:

flights = [[0,1,5],[1,2,5],[0,3,2],[3,1,2],[1,4,1],[4,2,1]], 
n = 5, 
src = 0, 
dst = 2, 
k = 2

The expected answer is 7, but my code is returning 9. I have spent the last 2 hours debugging the code, but I cant seem to find the issue. I would appreciate if someone can point out whats wrong with the code.

code:

class minHeap {
    constructor() {
        this.nodes = []
    }

    enqueue(node, priority, stopsCount) {
        if(this.isEmpty()) {
            this.nodes.push([node, priority, stopsCount])
        } else {
            let added = false;

            for(let i = 0; i < this.nodes.length; i++) {
                if(this.nodes[i][1] > priority) {
                    this.nodes.splice(i, 0, [node, priority, stopsCount])
                    added = true
                    break;
                }
            }

            if(!added) {
                this.nodes.push([node, priority, stopsCount])
            }
        }
    }

    dequeue() {
        return this.nodes.shift()
    }

    isEmpty() {
        return this.nodes.length === 0
    }
}

var findCheapestPrice = function(n, flights, src, dst, k) {
    const graph = {}
    const prices = {}
    for(let i = 0; i < n; i++) {
        graph[i] = []
        if (i == src) {
            prices[i] = 0
        } else {
            prices[i] = Infinity
        }
    }

    for(let [ from, to, price ] of flights) {
        graph[from].push([ to, price ])
    }


    const heap = new minHeap()
    heap.enqueue(src, 0, 0)
    
    while (!heap.isEmpty()) {
        const [ airport, price, stopsCount ] = heap.dequeue()
        
        for (let neighbor of graph[airport]) {
            const [ neighborAirport, neighborPrice ] = neighbor
            const priceToNeighbor = neighborPrice + price
            
            if (prices[neighborAirport] > priceToNeighbor && stopsCount <= k) {
                prices[neighborAirport] = priceToNeighbor
                heap.enqueue(neighborAirport, priceToNeighbor, stopsCount+1)
            }

        }
    }

    
    return prices[dst] == Infinity ? -1 : prices[dst]
};
Andrew Kim
  • 61
  • 6
  • Please include the problem statement. What is the input format? – infinitezero Jul 18 '23 at 04:45
  • here is the problem statement: "There are n cities connected by some number of flights. You are given an array flights where flights[i] = [fromi, toi, pricei] indicates that there is a flight from city fromi to city toi with cost pricei. You are also given three integers src, dst, and k, return the cheapest price from src to dst with at most k stops. If there is no such route, return -1." – Andrew Kim Jul 18 '23 at 04:57
  • I suppose you gave us the list of flights, but what is the source, destination and number of stops? – infinitezero Jul 18 '23 at 05:12
  • src = 0, dst = 2, k = 2 – Andrew Kim Jul 18 '23 at 05:19
  • Please [edit](https://stackoverflow.com/posts/76709586/edit) this into your question. I assumed you're coding in javascript. Please also add the language as a tag in your edit. – infinitezero Jul 18 '23 at 05:20
  • 1
    alright, got it – Andrew Kim Jul 18 '23 at 05:27
  • 1
    The only solution with 7 would be `k=3` (`0 -> 1 -> 4 -> 2 (5+1+1 = 7)`) unless I am missing something. Are you sure this is not a wrong test case? Also with `k=2` I don't know how your code produces 9, because I can only see 1 solution (`0 -> 1 -> 2 = 10 `), the other path `0 -> 3 -> deadend` does not work. – Luke Vo Jul 18 '23 at 05:37
  • 1
    Its because the problem only counts nodes between src and dst so in path 0 -> 1 -> 4 -> 2, it would ony count nodess between 0 and 2 so 1 and 4 – Andrew Kim Jul 18 '23 at 05:43

2 Answers2

2

I don't have enough time to debug exactly what's wrong but I feel something is wrong with your enqueuing priority. Removing that optimization causes the program to work correctly:

class minHeap {
    constructor() {
        this.nodes = []
    }

    enqueue(node, priority, stopsCount) {
        // This now work without the priority.
        this.nodes.push([node, priority, stopsCount]);
    }

    dequeue() {
        return this.nodes.shift()
    }

    isEmpty() {
        return this.nodes.length === 0
    }
}

var findCheapestPrice = function(n, flights, src, dst, k) {
    const graph = {}
    const prices = {}
    for(let i = 0; i < n; i++) {
        graph[i] = []
        if (i == src) {
            prices[i] = 0
        } else {
            prices[i] = Infinity
        }
    }

    for(let [ from, to, price ] of flights) {
        graph[from].push([ to, price ])
    }


    const heap = new minHeap()
    heap.enqueue(src, 0, 0)

    while (!heap.isEmpty()) {
        const [ airport, price, stopsCount ] = heap.dequeue()
        
        for (let neighbor of graph[airport]) {
            const [ neighborAirport, neighborPrice ] = neighbor
            const priceToNeighbor = neighborPrice + price
            
            if (prices[neighborAirport] > priceToNeighbor && stopsCount <= k) {
                prices[neighborAirport] = priceToNeighbor
                heap.enqueue(neighborAirport, priceToNeighbor, stopsCount+1)
            }

        }
    }

    
    return prices[dst] == Infinity ? -1 : prices[dst]
};

const
  flights = [[0,1,5],[1,2,5],[0,3,2],[3,1,2],[1,4,1],[4,2,1]], 
  n = 6, 
  src = 0, 
  dst = 2, 
  k = 2;
console.log(findCheapestPrice(n, flights, src, dst, k));

I tried submitting the above code to LeetCode (sorry about that, I didn't mean to plagarize, just want to test), it passes all test cases.

Update: this fix may work by luck though I cannot find a counter example yet. In fact, I think without the priority, it should work for all cases (see comments below and the other answer). Even with this complicated graph this code works:

enter image description here

Luke Vo
  • 17,859
  • 21
  • 105
  • 181
  • wait what..? i'll try debugging it myself, thanks for the help – Andrew Kim Jul 18 '23 at 06:02
  • @AndrewKim I thought of a possibility. The fix I posted may be just a lucky case where there is no test case against it. There is possible that, a longer sub-route is cheaper but longer (and thus is not included in the final result). For example, you may go from 0 to 1 at the cost of 5 BUT it needs 2 stops and then you stop considering more expensive routes but only take 1 stop. The proper way IMO is to store the cost AND the number of steps. – Luke Vo Jul 18 '23 at 06:24
  • so like have a seperate object for number of steps, we can call it steps for now, and everytime we visit nodes neighbor we set steps[neighbor] = steps[node] + 1. – Andrew Kim Jul 18 '23 at 06:34
  • @גלעדברקן yep I agree, as I mentioned above. – Luke Vo Jul 18 '23 at 09:06
  • @גלעדברקן I have yet to find a counter example. AND I think it actually works because the way `enqueue` work, when a cheaper but **longer** route is found, it's processed AFTER the shorter but expensive route is found and so if `k` falls short, that value does not reach to the end. So maybe there is no luck involved here. – Luke Vo Jul 18 '23 at 09:50
  • The length wasn't the issue I saw. It was that a state can get set for a node that is a smaller number than the state that specific node needs to be for the optimal route. See my answer. – גלעד ברקן Jul 18 '23 at 09:52
  • Does this work because it's actually a breadth-first search, each potential additional step always going to the end of the queue? – גלעד ברקן Jul 18 '23 at 10:19
  • @גלעדברקן yes that's why. – Luke Vo Jul 18 '23 at 10:47
1

The scheme to update and only enqueue stops prioritised by cost doesn't work in this particular example because the cost to get to airport 4 is set as 5 after 3 stops (including the last): [0,3,2] -> [3,1,2] -> [1,4,1]. But in order to reach airport 2 optimally, we need the interim state where we reach airport 4 with cost 6 but the priority scheme prevents this:

[0,1,5] -> [1,4,1]

I don't believe Dijkstra can be used generally to optimise edge cost to a destination where the number of edges is restricted. Other dynamic programming or graph algorithms can assist with that.

גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61