Saturday Night Dijkstra's Algorithm

Covid days (and nights) are still amongst us, especially now with the sucky Omicron Variant. Stuck at home, yet again, we resort to our dear friend Dijkstra:


In this latest problem on Leetcode (959th solved), we see another by-the-book invitation to solve a problem using Dijkstra's Algorithm. There is a graph, with weights, and you want to minimize the cost (I'm sure there is a DP algorithm for that too, but I'm not that good in DP as I mentioned many times before). The problem adds a "tweak" since now you have a max of N discounts that you can apply. This really doesn't change the problem that much: in each interaction, you should assume (a) adding a new connection with the discount (if allowed) and (b) adding a new connection without. So max of two potential enqueue calls for each connection.

Recapping what Dijkstra's is:
1/ It is a BFS algorithm
2/ But it uses a Priority Queue, rather than just a Queue
3/ You only mark as "visited" during the dequeue - never during the enqueue
4/ You allow the same connected node to be enqueued multiple times (different weights), that's why you never mark as visited during the enqueue
5/ Time complexity is of the order O((V+E)LogV), where V is the number of vertices and E the number of edges. In our case, V = E = 1000, hence O(2000*Log(1000)), very fast.

Problem and code are below, cheers, and stay safe! ACC.

Minimum Cost to Reach City With Discounts - LeetCode

2093. Minimum Cost to Reach City With Discounts
Medium

A series of highways connect n cities numbered from 0 to n - 1. You are given a 2D integer array highways where highways[i] = [city1i, city2i, tolli] indicates that there is a highway that connects city1i and city2i, allowing a car to go from city1i to city2i and vice versa for a cost of tolli.

You are also given an integer discounts which represents the number of discounts you have. You can use a discount to travel across the ith highway for a cost of tolli / 2 (integer division). Each discount may only be used once, and you can only use at most one discount per highway.

Return the minimum total cost to go from city 0 to city n - 1, or -1 if it is not possible to go from city 0 to city n - 1.

 

Example 1:

Input: n = 5, highways = [[0,1,4],[2,1,3],[1,4,11],[3,2,3],[3,4,2]], discounts = 1
Output: 9
Explanation:
Go from 0 to 1 for a cost of 4.
Go from 1 to 4 and use a discount for a cost of 11 / 2 = 5.
The minimum cost to go from 0 to 4 is 4 + 5 = 9.

Example 2:

Input: n = 4, highways = [[1,3,17],[1,2,7],[3,2,5],[0,1,6],[3,0,20]], discounts = 20
Output: 8
Explanation:
Go from 0 to 1 and use a discount for a cost of 6 / 2 = 3.
Go from 1 to 2 and use a discount for a cost of 7 / 2 = 3.
Go from 2 to 3 and use a discount for a cost of 5 / 2 = 2.
The minimum cost to go from 0 to 3 is 3 + 3 + 2 = 8.

Example 3:

Input: n = 4, highways = [[0,1,3],[2,3,2]], discounts = 0
Output: -1
Explanation:
It is impossible to go from 0 to 3 so return -1.

 

Constraints:

  • 2 <= n <= 1000
  • 1 <= highways.length <= 1000
  • highways[i].length == 3
  • 0 <= city1i, city2i <= n - 1
  • city1i != city2i
  • 0 <= tolli <= 105
  • 0 <= discounts <= 500
  • There are no duplicate highways.
Accepted
310
Submissions
457




public class Solution
{
    public class PriorityQueue
    {
        public struct HeapEntry
        {
            private object item;
            private double priority;
            public HeapEntry(object item, double priority)
            {
                this.item = item;
                this.priority = priority;
            }
            public object Item
            {
                get
                {
                    return item;
                }
            }
            public double Priority
            {
                get
                {
                    return priority;
                }
            }
        }

        private bool ascend;
        private int count;
        private int capacity;
        private HeapEntry[] heap;

        public int Count
        {
            get
            {
                return this.count;
            }
        }

        public PriorityQueue(bool ascend)
        {
            capacity = 1000000;
            heap = new HeapEntry[capacity];
            this.ascend = ascend;
        }

        public object Dequeue(out double priority)
        {
            priority = heap[0].Priority;
            object result = heap[0].Item;
            count--;
            trickleDown(0, heap[count]);
            return result;
        }

        public object Peak(/*out double priority*/)
        {
            //priority = heap[0].Priority;
            object result = heap[0].Item;
            return result;
        }

        public void Enqueue(object item, double priority)
        {
            count++;
            bubbleUp(count - 1, new HeapEntry(item, priority));
        }

        private void bubbleUp(int index, HeapEntry he)
        {
            int parent = (index - 1) / 2;
            // note: (index > 0) means there is a parent
            if (this.ascend)
            {
                while ((index > 0) && (heap[parent].Priority > he.Priority))
                {
                    heap[index] = heap[parent];
                    index = parent;
                    parent = (index - 1) / 2;
                }
                heap[index] = he;
            }
            else
            {
                while ((index > 0) && (heap[parent].Priority < he.Priority))
                {
                    heap[index] = heap[parent];
                    index = parent;
                    parent = (index - 1) / 2;
                }
                heap[index] = he;
            }
        }

        private void trickleDown(int index, HeapEntry he)
        {
            int child = (index * 2) + 1;
            while (child < count)
            {
                if (this.ascend)
                {
                    if (((child + 1) < count) &&
                        (heap[child].Priority > heap[child + 1].Priority))
                    {
                        child++;
                    }
                }
                else
                {
                    if (((child + 1) < count) &&
                        (heap[child].Priority < heap[child + 1].Priority))
                    {
                        child++;
                    }
                }
                heap[index] = heap[child];
                index = child;
                child = (index * 2) + 1;
            }
            bubbleUp(index, he);
        }
    }

    public class MCQueueItem
    {
        public int city = 0;
        public int totalCost = 0;
        public int discountsLeft = 0;

        public MCQueueItem(int city, int totalCost, int discountsLeft)
        {
            this.city = city;
            this.totalCost = totalCost;
            this.discountsLeft = discountsLeft;
        }
    }
    public int MinimumCost(int n, int[][] highways, int discounts)
    {
        Hashtable graph = new Hashtable();

        for (int i = 0; i < highways.Length; i++)
        {
            int[] cities = { highways[i][0], highways[i][1] };
            for (int j = 0; j < cities.Length; j++)
            {
                if (!graph.ContainsKey(cities[j])) graph.Add(cities[j], new Hashtable());
                Hashtable connections = (Hashtable)graph[cities[j]];
                if (!connections.ContainsKey(cities[(j + 1) % 2])) connections.Add(cities[(j + 1) % 2], highways[i][2]);
            }
        }

        int startCity = 0;
        int finishCity = n - 1;
        PriorityQueue pQueue = new PriorityQueue(true);
        Hashtable visited = new Hashtable();
        MCQueueItem queueItem = new MCQueueItem(startCity, 0, discounts);
        pQueue.Enqueue(queueItem, queueItem.totalCost);

        while (pQueue.Count > 0)
        {
            double tempTotalCost = 0;
            MCQueueItem currentCity = (MCQueueItem)pQueue.Dequeue(out tempTotalCost);

            //When using Djkistra, only mark during the dequeue, not during the enqueue
            if (!visited.ContainsKey(currentCity.city)) visited.Add(currentCity.city, true);

            if (currentCity.city == finishCity)
            {
                return currentCity.totalCost;
            }

            if (graph.ContainsKey(currentCity.city))
            {
                Hashtable connections = (Hashtable)graph[currentCity.city];
                foreach (int connectedCity in connections.Keys)
                {
                    if (!visited.ContainsKey(connectedCity))
                    {
                        //Two cases to add:
                        //1. applying the discount
                        if (currentCity.discountsLeft > 0)
                        {
                            MCQueueItem queueItem1 = new MCQueueItem(connectedCity, currentCity.totalCost + ((int)connections[connectedCity]) / 2, currentCity.discountsLeft - 1);
                            pQueue.Enqueue(queueItem1, queueItem1.totalCost);
                        }

                        //2. not applying the discount
                        MCQueueItem queueItem2 = new MCQueueItem(connectedCity, currentCity.totalCost + (int)connections[connectedCity], currentCity.discountsLeft);
                        pQueue.Enqueue(queueItem2, queueItem2.totalCost);
                    }
                }
            }
        }

        return -1;
    }
}


Comments

Popular posts from this blog

Advent of Code - Day 6, 2024: BFS and FSM

Golang vs. C#: performance characteristics (simple case study)

Advent of Code - Day 7, 2024: Backtracking and Eval