Facebook Pixel

2473. Minimum Cost to Buy Apples 🔒

Problem Description

You have n cities numbered from 1 to n. These cities are connected by bidirectional roads given in a 2D array roads, where roads[i] = [ai, bi, costi] means there's a road between city ai and city bi with travel cost costi.

Each city sells apples at different prices. The array appleCost tells you the price of an apple in each city, where appleCost[i] is the cost to buy one apple from city i (using 0-based indexing in the actual implementation).

Here's the task:

  1. Start from any city
  2. Travel through roads to reach any city (could be the starting city itself)
  3. Buy exactly one apple from that city
  4. Return back to your starting city

The catch is that after buying the apple, all road costs are multiplied by a factor k for your return journey. So if a road originally costs c, it will cost c * k on the way back.

Given the multiplier k, you need to find the minimum total cost for each possible starting city. Return an array answer where answer[i] represents the minimum cost to complete the entire journey (go somewhere, buy an apple, and return) if you start from city i.

The total cost for a journey includes:

  • Cost to travel from starting city to the city where you buy the apple
  • Cost of the apple itself
  • Cost to travel back (with road costs multiplied by k)

Since the same roads are used for going and returning, if the shortest path from city A to city B costs d, then the total travel cost would be d + d * k = d * (1 + k).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that we need to find the optimal city to buy an apple from, for each possible starting city. Since we must return to our starting point after buying the apple, and the return journey costs are multiplied by k, the total travel cost becomes distance * (1 + k).

Think about what happens when we start from city i:

  • We need to reach some city j to buy an apple
  • The shortest path from i to j has some cost d
  • We buy an apple at city j for appleCost[j]
  • We return via the same shortest path, but now it costs d * k
  • Total cost = d + appleCost[j] + d * k = d * (1 + k) + appleCost[j]

This means for each starting city, we want to minimize: distance_to_city * (1 + k) + apple_cost_at_city

Since we need to evaluate this for every possible destination city from each starting point, we need the shortest distances from each starting city to all other cities. This naturally leads us to use Dijkstra's algorithm.

Why Dijkstra? Because:

  1. We need shortest paths from a single source (the starting city) to all other cities
  2. All edge weights (road costs) are positive
  3. We can calculate the minimum cost on-the-fly as we explore cities

During the Dijkstra traversal from starting city i, when we reach any city u with shortest distance d, we can immediately calculate the total cost if we buy an apple there: d * (k + 1) + appleCost[u]. We keep track of the minimum among all such costs.

The beauty of this approach is that Dijkstra guarantees we visit each city with its shortest distance from the source, so we can confidently calculate the cost for buying an apple at each city and maintain the global minimum.

Learn more about Graph, Shortest Path and Heap (Priority Queue) patterns.

Solution Approach

The solution implements a heap-optimized Dijkstra's algorithm for each city as the starting point. Here's how the implementation works:

Graph Construction: First, we build an adjacency list representation of the graph:

g = defaultdict(list)
for a, b, c in roads:
    a, b = a - 1, b - 1  # Convert to 0-based indexing
    g[a].append((b, c))
    g[b].append((a, c))

Since roads are bidirectional, we add edges in both directions. The cities are converted from 1-based to 0-based indexing for easier array manipulation.

Dijkstra's Algorithm for Each Starting City: For each city i from 0 to n-1, we run a modified Dijkstra's algorithm:

def dijkstra(i):
    q = [(0, i)]  # Priority queue: (distance, city)
    dist = [inf] * n  # Shortest distances from city i
    dist[i] = 0
    ans = inf  # Minimum cost to complete the journey

The algorithm uses:

  • A min-heap q to always process the city with minimum distance first
  • A dist array to track the shortest distance from starting city i to all other cities
  • An ans variable to track the minimum total cost found so far

Core Algorithm Logic:

while q:
    d, u = heappop(q)
    ans = min(ans, appleCost[u] + d * (k + 1))
    for v, w in g[u]:
        if dist[v] > dist[u] + w:
            dist[v] = dist[u] + w
            heappush(q, (dist[v], v))

For each city u we visit:

  1. Calculate the total cost if we buy an apple here: appleCost[u] + d * (k + 1)
    • d is the shortest distance from starting city to u
    • d * (k + 1) represents the round-trip cost (going costs d, returning costs d * k)
  2. Update ans with the minimum cost found
  3. Explore neighbors and update their distances if we found a shorter path
  4. Add updated neighbors to the priority queue

Final Result:

return [dijkstra(i) for i in range(n)]

We run Dijkstra from each city and collect all minimum costs in a list.

Time Complexity: O(n * (n + m) * log n) where n is the number of cities and m is the number of roads. We run Dijkstra n times, and each run takes O((n + m) * log n) with the heap operations.

Space Complexity: O(n + m) for storing the graph and the distance array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Given:

  • n = 4 cities (numbered 1 to 4)
  • roads = [[1,2,4], [2,3,2], [2,4,5], [3,4,1]]
  • appleCost = [56, 42, 86, 23] (0-indexed: city 1 costs 56, city 2 costs 42, etc.)
  • k = 2 (return journey costs are doubled)

Graph Visualization:

    1 --4-- 2
           /|\
          2 | 5
           \|/
            3 --1-- 4

Step 1: Convert to 0-based indexing and build adjacency list

City 0: [(1, 4)]
City 1: [(0, 4), (2, 2), (3, 5)]
City 2: [(1, 2), (3, 1)]
City 3: [(1, 5), (2, 1)]

Step 2: Run Dijkstra from City 0 (originally City 1)

Initialize:

  • Priority queue: [(0, 0)] (distance 0 to itself)
  • Distances: [0, inf, inf, inf]
  • Minimum cost: inf

Processing:

  1. Pop (0, 0) - at city 0 with distance 0

    • Cost if buying here: 56 + 0*(2+1) = 56
    • Update minimum: ans = 56
    • Explore neighbor city 1: update distance to 4, push (4, 1)
  2. Pop (4, 1) - at city 1 with distance 4

    • Cost if buying here: 42 + 4*3 = 54
    • Update minimum: ans = 54
    • Explore neighbors:
      • City 2: distance becomes 6, push (6, 2)
      • City 3: distance becomes 9, push (9, 3)
  3. Pop (6, 2) - at city 2 with distance 6

    • Cost if buying here: 86 + 6*3 = 104
    • Minimum stays: ans = 54
    • Explore city 3: distance becomes 7 (better than 9), push (7, 3)
  4. Pop (7, 3) - at city 3 with distance 7

    • Cost if buying here: 23 + 7*3 = 44
    • Update minimum: ans = 44

Result for starting from city 0: 44 (buy apple at city 3)

Step 3: Repeat for other starting cities

Starting from City 1 (originally City 2):

  • Shortest paths: [4, 0, 2, 3]
  • Costs: City 0: 56+12=68, City 1: 42+0=42, City 2: 86+6=92, City 3: 23+9=32
  • Minimum: 32

Starting from City 2 (originally City 3):

  • Shortest paths: [6, 2, 0, 1]
  • Costs: City 0: 56+18=74, City 1: 42+6=48, City 2: 86+0=86, City 3: 23+3=26
  • Minimum: 26

Starting from City 3 (originally City 4):

  • Shortest paths: [7, 3, 1, 0]
  • Costs: City 0: 56+21=77, City 1: 42+9=51, City 2: 86+3=89, City 3: 23+0=23
  • Minimum: 23

Final Answer: [44, 32, 26, 23]

This means:

  • Starting from city 1: minimum cost is 44 (go to city 4, buy apple for 23, return)
  • Starting from city 2: minimum cost is 32 (go to city 4, buy apple for 23, return)
  • Starting from city 3: minimum cost is 26 (go to city 4, buy apple for 23, return)
  • Starting from city 4: minimum cost is 23 (buy apple locally, no travel needed)

Solution Implementation

1from heapq import heappush, heappop
2from collections import defaultdict
3from typing import List
4from math import inf
5
6class Solution:
7    def minCost(
8        self, n: int, roads: List[List[int]], appleCost: List[int], k: int
9    ) -> List[int]:
10        """
11        Find minimum cost to buy an apple starting from each city.
12      
13        Args:
14            n: Number of cities (0-indexed)
15            roads: List of [city1, city2, cost] representing bidirectional roads (1-indexed)
16            appleCost: Cost of buying an apple in each city
17            k: Cost factor for traveling (total travel cost = road_cost * (k + 1))
18      
19        Returns:
20            List where result[i] is minimum cost to buy an apple starting from city i
21        """
22      
23        def dijkstra(start_city: int) -> int:
24            """
25            Use Dijkstra's algorithm to find minimum cost to buy an apple starting from start_city.
26          
27            Args:
28                start_city: The starting city index (0-indexed)
29          
30            Returns:
31                Minimum cost to buy an apple considering all reachable cities
32            """
33            # Priority queue: (distance_from_start, city)
34            priority_queue = [(0, start_city)]
35          
36            # Track shortest distance from start_city to each city
37            shortest_distances = [inf] * n
38            shortest_distances[start_city] = 0
39          
40            # Track minimum cost to buy an apple (including travel and purchase cost)
41            min_apple_cost = inf
42          
43            while priority_queue:
44                current_distance, current_city = heappop(priority_queue)
45              
46                # Calculate total cost: apple price + travel cost * (k + 1)
47                total_cost = appleCost[current_city] + current_distance * (k + 1)
48                min_apple_cost = min(min_apple_cost, total_cost)
49              
50                # Explore neighbors
51                for neighbor_city, road_cost in adjacency_list[current_city]:
52                    new_distance = shortest_distances[current_city] + road_cost
53                  
54                    # If we found a shorter path to neighbor, update it
55                    if shortest_distances[neighbor_city] > new_distance:
56                        shortest_distances[neighbor_city] = new_distance
57                        heappush(priority_queue, (new_distance, neighbor_city))
58          
59            return min_apple_cost
60      
61        # Build adjacency list for the graph (convert to 0-indexed)
62        adjacency_list = defaultdict(list)
63        for city1, city2, road_cost in roads:
64            # Convert from 1-indexed to 0-indexed
65            city1_idx = city1 - 1
66            city2_idx = city2 - 1
67          
68            # Add bidirectional edges
69            adjacency_list[city1_idx].append((city2_idx, road_cost))
70            adjacency_list[city2_idx].append((city1_idx, road_cost))
71      
72        # Calculate minimum cost for each starting city
73        result = [dijkstra(city) for city in range(n)]
74        return result
75
1class Solution {
2    // Constants and instance variables
3    private static final int INF = 0x3f3f3f3f;  // Infinity value for initialization
4    private int travelCostMultiplier;           // Multiplier for travel costs (k)
5    private int[] appleCosts;                   // Cost of apples at each city
6    private int[] distances;                    // Shortest distances from source
7    private List<int[]>[] adjacencyList;        // Graph representation using adjacency list
8  
9    /**
10     * Calculate minimum cost to buy apples from each city
11     * @param n Number of cities
12     * @param roads Array of roads where each road is [city1, city2, cost]
13     * @param appleCost Cost of apples in each city
14     * @param k Travel cost multiplier
15     * @return Array of minimum costs for buying apples starting from each city
16     */
17    public long[] minCost(int n, int[][] roads, int[] appleCost, int k) {
18        // Initialize instance variables
19        this.appleCosts = appleCost;
20        this.travelCostMultiplier = k;
21        this.distances = new int[n];
22        this.adjacencyList = new List[n];
23      
24        // Initialize adjacency list for each city
25        for (int i = 0; i < n; ++i) {
26            adjacencyList[i] = new ArrayList<>();
27        }
28      
29        // Build the undirected graph from roads
30        for (int[] road : roads) {
31            int cityA = road[0] - 1;  // Convert to 0-indexed
32            int cityB = road[1] - 1;  // Convert to 0-indexed
33            int roadCost = road[2];
34          
35            // Add bidirectional edges
36            adjacencyList[cityA].add(new int[] {cityB, roadCost});
37            adjacencyList[cityB].add(new int[] {cityA, roadCost});
38        }
39      
40        // Calculate minimum cost for each starting city
41        long[] result = new long[n];
42        for (int startCity = 0; startCity < n; ++startCity) {
43            result[startCity] = dijkstra(startCity);
44        }
45      
46        return result;
47    }
48  
49    /**
50     * Use Dijkstra's algorithm to find minimum cost to buy apples starting from given city
51     * @param startCity The starting city index
52     * @return Minimum total cost (travel + apple cost) from the starting city
53     */
54    private long dijkstra(int startCity) {
55        // Priority queue to store [distance, city] pairs, ordered by distance
56        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a, b) -> a[0] - b[0]);
57      
58        // Initialize with starting city at distance 0
59        priorityQueue.offer(new int[] {0, startCity});
60      
61        // Initialize all distances to infinity
62        Arrays.fill(distances, INF);
63        distances[startCity] = 0;
64      
65        // Track minimum cost to buy apples
66        long minimumCost = Long.MAX_VALUE;
67      
68        // Process cities in order of increasing distance
69        while (!priorityQueue.isEmpty()) {
70            int[] current = priorityQueue.poll();
71            int currentDistance = current[0];
72            int currentCity = current[1];
73          
74            // Calculate total cost: apple cost + travel cost
75            // Travel cost = (k + 1) * distance (going and returning)
76            long totalCost = appleCosts[currentCity] + (long)(travelCostMultiplier + 1) * currentDistance;
77            minimumCost = Math.min(minimumCost, totalCost);
78          
79            // Explore neighbors of current city
80            for (int[] neighbor : adjacencyList[currentCity]) {
81                int nextCity = neighbor[0];
82                int edgeWeight = neighbor[1];
83                int newDistance = distances[currentCity] + edgeWeight;
84              
85                // Update distance if we found a shorter path
86                if (distances[nextCity] > newDistance) {
87                    distances[nextCity] = newDistance;
88                    priorityQueue.offer(new int[] {distances[nextCity], nextCity});
89                }
90            }
91        }
92      
93        return minimumCost;
94    }
95}
96
1using ll = long long;
2using pii = pair<int, int>;
3
4class Solution {
5public:
6    const int INF = 0x3f3f3f3f;
7
8    vector<long long> minCost(int n, vector<vector<int>>& roads, vector<int>& appleCost, int k) {
9        // Build adjacency list representation of the graph
10        // Each city stores its connected cities with road costs
11        vector<vector<pii>> graph(n);
12        for (auto& road : roads) {
13            int cityA = road[0] - 1;  // Convert to 0-indexed
14            int cityB = road[1] - 1;  // Convert to 0-indexed
15            int roadCost = road[2];
16          
17            // Add bidirectional edges
18            graph[cityA].push_back({cityB, roadCost});
19            graph[cityB].push_back({cityA, roadCost});
20        }
21      
22        // Lambda function to run Dijkstra's algorithm from a starting city
23        // Returns minimum cost to buy an apple starting from the given city
24        auto dijkstra = [&](int startCity) -> long long {
25            // Initialize distances to all cities as infinity
26            int distances[n];
27            memset(distances, 63, sizeof(distances));
28          
29            // Min-heap priority queue: {distance, city}
30            priority_queue<pii, vector<pii>, greater<pii>> minHeap;
31          
32            // Start from the given city with distance 0
33            minHeap.push({0, startCity});
34            distances[startCity] = 0;
35          
36            // Track minimum cost to buy an apple
37            // Cost = apple price + travel cost * (k + 1)
38            // where k is the cost multiplier per unit distance
39            ll minAppleCost = LONG_MAX;
40          
41            // Process cities in order of increasing distance
42            while (!minHeap.empty()) {
43                auto [currentDist, currentCity] = minHeap.top();
44                minHeap.pop();
45              
46                // Calculate total cost if we buy apple at current city
47                // Formula: apple cost + (travel distance * (k + 1))
48                ll totalCost = appleCost[currentCity] + 1LL * currentDist * (k + 1);
49                minAppleCost = min(minAppleCost, totalCost);
50              
51                // Explore neighbors of current city
52                for (auto& [neighborCity, edgeWeight] : graph[currentCity]) {
53                    int newDistance = distances[currentCity] + edgeWeight;
54                  
55                    // If we found a shorter path to the neighbor, update it
56                    if (distances[neighborCity] > newDistance) {
57                        distances[neighborCity] = newDistance;
58                        minHeap.push({newDistance, neighborCity});
59                    }
60                }
61            }
62          
63            return minAppleCost;
64        };
65      
66        // Calculate minimum cost for each city as starting point
67        vector<ll> result(n);
68        for (int city = 0; city < n; ++city) {
69            result[city] = dijkstra(city);
70        }
71      
72        return result;
73    }
74};
75
1type Long = number;
2type Pair = [number, number];
3
4const INF = 0x3f3f3f3f;
5
6function minCost(n: number, roads: number[][], appleCost: number[], k: number): number[] {
7    // Build adjacency list representation of the graph
8    // Each city stores its connected cities with road costs
9    const graph: Pair[][] = Array(n).fill(null).map(() => []);
10  
11    for (const road of roads) {
12        const cityA = road[0] - 1;  // Convert to 0-indexed
13        const cityB = road[1] - 1;  // Convert to 0-indexed
14        const roadCost = road[2];
15      
16        // Add bidirectional edges
17        graph[cityA].push([cityB, roadCost]);
18        graph[cityB].push([cityA, roadCost]);
19    }
20  
21    // Function to run Dijkstra's algorithm from a starting city
22    // Returns minimum cost to buy an apple starting from the given city
23    const dijkstra = (startCity: number): Long => {
24        // Initialize distances to all cities as infinity
25        const distances: number[] = new Array(n).fill(INF);
26      
27        // Min-heap priority queue: [distance, city]
28        // Using array with manual sorting to simulate priority queue
29        const minHeap: Pair[] = [];
30      
31        // Helper function to add to heap and maintain min-heap property
32        const heapPush = (item: Pair): void => {
33            minHeap.push(item);
34            minHeap.sort((a, b) => a[0] - b[0]);
35        };
36      
37        // Start from the given city with distance 0
38        heapPush([0, startCity]);
39        distances[startCity] = 0;
40      
41        // Track minimum cost to buy an apple
42        // Cost = apple price + travel cost * (k + 1)
43        // where k is the cost multiplier per unit distance
44        let minAppleCost: Long = Number.MAX_SAFE_INTEGER;
45      
46        // Process cities in order of increasing distance
47        while (minHeap.length > 0) {
48            const [currentDist, currentCity] = minHeap.shift()!;
49          
50            // Skip if we've already processed this city with a shorter distance
51            if (currentDist > distances[currentCity]) {
52                continue;
53            }
54          
55            // Calculate total cost if we buy apple at current city
56            // Formula: apple cost + (travel distance * (k + 1))
57            const totalCost: Long = appleCost[currentCity] + currentDist * (k + 1);
58            minAppleCost = Math.min(minAppleCost, totalCost);
59          
60            // Explore neighbors of current city
61            for (const [neighborCity, edgeWeight] of graph[currentCity]) {
62                const newDistance = distances[currentCity] + edgeWeight;
63              
64                // If we found a shorter path to the neighbor, update it
65                if (distances[neighborCity] > newDistance) {
66                    distances[neighborCity] = newDistance;
67                    heapPush([newDistance, neighborCity]);
68                }
69            }
70        }
71      
72        return minAppleCost;
73    };
74  
75    // Calculate minimum cost for each city as starting point
76    const result: Long[] = new Array(n);
77    for (let city = 0; city < n; city++) {
78        result[city] = dijkstra(city);
79    }
80  
81    return result;
82}
83

Time and Space Complexity

Time Complexity: O(n × m × log m)

The algorithm runs Dijkstra's algorithm from each of the n cities. For each Dijkstra execution:

  • The while loop processes each edge at most once, giving us O(m) edge relaxations
  • Each edge relaxation involves a heap push operation, which costs O(log |heap|)
  • The heap can contain at most m elements (since we can push at most once per edge relaxation)
  • Therefore, each Dijkstra run takes O(m × log m) time

Since we run Dijkstra n times (once for each starting city), the total time complexity is O(n × m × log m).

Space Complexity: O(n + m)

The space usage consists of:

  • Graph adjacency list g: stores all edges, requiring O(m) space (each edge is stored twice for undirected graph)
  • Distance array dist: requires O(n) space for each Dijkstra run
  • Priority queue q: can contain at most O(m) elements in the worst case
  • Result list: requires O(n) space

Since these data structures don't scale multiplicatively and the Dijkstra function reuses the same space for each iteration, the overall space complexity is O(n + m).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrect Distance Check in Dijkstra's Algorithm

The Problem: The current implementation has a subtle but critical bug in the Dijkstra's algorithm. When we pop a node from the priority queue, we don't verify if we've already processed this node with a shorter distance. This can lead to processing the same node multiple times with outdated distances, causing incorrect results.

# Current problematic code:
while priority_queue:
    current_distance, current_city = heappop(priority_queue)
    # No check if this is an outdated entry!
    total_cost = appleCost[current_city] + current_distance * (k + 1)
    min_apple_cost = min(min_apple_cost, total_cost)

Why This Happens: In Dijkstra's algorithm with a min-heap, we might push the same city multiple times with different distances. When we later pop an outdated entry (with a larger distance), we shouldn't process it again.

The Solution: Add a check to skip processing if the current distance is greater than the already recorded shortest distance:

while priority_queue:
    current_distance, current_city = heappop(priority_queue)
  
    # Skip if this is an outdated entry
    if current_distance > shortest_distances[current_city]:
        continue
  
    total_cost = appleCost[current_city] + current_distance * (k + 1)
    min_apple_cost = min(min_apple_cost, total_cost)

Pitfall 2: Integer Overflow with Large k Values

The Problem: When calculating current_distance * (k + 1), if both current_distance and k are large, the multiplication might overflow in languages with fixed integer sizes. While Python handles arbitrary precision integers, this is a common issue when porting to other languages.

The Solution: Be mindful of the constraints and consider using appropriate data types or adding overflow checks:

# For very large values, consider checking bounds
total_cost = appleCost[current_city] + current_distance * (k + 1)
if total_cost > 10**15:  # Or some reasonable upper bound
    continue  # This path is too expensive anyway

Pitfall 3: Not Handling Edge Cases

The Problem: The code doesn't explicitly handle certain edge cases:

  • What if a city has no roads connecting to it? (isolated city)
  • What if k = 0? (no extra cost for return journey)

The Solution: The current implementation actually handles these cases correctly:

  • Isolated cities will only consider buying an apple from themselves (distance = 0)
  • When k = 0, the formula d * (k + 1) = d * 1 = d correctly represents just the one-way cost

However, it's good practice to document or test these edge cases explicitly:

# Edge case: buying apple from starting city itself
# This is automatically handled as shortest_distances[start_city] = 0
# Cost = appleCost[start_city] + 0 * (k + 1) = appleCost[start_city]

Complete Corrected Solution:

def dijkstra(start_city: int) -> int:
    priority_queue = [(0, start_city)]
    shortest_distances = [inf] * n
    shortest_distances[start_city] = 0
    min_apple_cost = inf
  
    while priority_queue:
        current_distance, current_city = heappop(priority_queue)
      
        # Critical fix: Skip outdated entries
        if current_distance > shortest_distances[current_city]:
            continue
      
        total_cost = appleCost[current_city] + current_distance * (k + 1)
        min_apple_cost = min(min_apple_cost, total_cost)
      
        for neighbor_city, road_cost in adjacency_list[current_city]:
            new_distance = shortest_distances[current_city] + road_cost
          
            if shortest_distances[neighbor_city] > new_distance:
                shortest_distances[neighbor_city] = new_distance
                heappush(priority_queue, (new_distance, neighbor_city))
  
    return min_apple_cost
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More