Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

2247. Maximum Cost of Trip With K Highways - Leetcode Solution

Problem Description

You are given a set of n cities, labeled from 0 to n - 1, and a list of highways. Each highway connects two different cities and has a positive integer cost. The highways list is given as an array of tuples/lists [u, v, cost], meaning a highway connects city u and city v with cost cost.

You are also given an integer k. Your task is to select at most k highways (no city can be connected by more than one selected highway) to form a trip, such that the total cost of the selected highways is maximized.

  • Each city can be used at most once in the selected highways.
  • You may select fewer than k highways if it is optimal.
  • Return the maximum total cost achievable under these constraints.

Thought Process

At first glance, the problem appears similar to selecting edges in a graph to maximize the sum under certain constraints. A brute-force approach would be to try all subsets of highways of size up to k and check if their cities do not overlap, then take the maximum sum. However, this is not practical for large inputs due to the exponential number of subsets.

The key insight is that each selected highway must connect two unique cities not used elsewhere. This is equivalent to finding a matching in the graph (a set of edges without common vertices), with the additional constraint that the matching size is at most k and the sum of costs is maximized.

This is known as the "Maximum Weight Matching" problem (with a limit on the number of edges). For small n or k, we can use bitmask dynamic programming (DP). For larger cases, greedy or advanced graph algorithms may be necessary.

Solution Approach

Let's break down the approach step by step:

  1. Model the Problem as a Matching: Each highway is an edge, and we want to select up to k edges so that no two edges share a city, and the sum of their costs is maximized.
  2. Bitmask Dynamic Programming (DP): For small n (typically n ≤ 16), we can represent the set of used cities as a bitmask. For each subset of cities, we can store the maximum total cost achievable by selecting highways that only use those cities.
  3. DP State: dp[mask] is the max cost achievable using the set of cities represented by mask.
  4. Transition: For each pair of cities u and v connected by a highway, if both are not used in mask, we can consider adding this highway and update dp[mask | (1<<u) | (1<<v)].
  5. Iterate: We iterate over all possible masks and all highways, updating the DP table accordingly.
  6. Result: The answer is the maximum dp[mask] where the number of set bits in mask is at most 2*k (since each highway uses two cities).

For larger n, a greedy approach (sorting highways by cost and greedily picking non-overlapping ones) may be used, but it is not always optimal. For the scope of this explanation, we focus on the bitmask DP, which is optimal for small n.

Example Walkthrough

Suppose n = 4, highways = [[0,1,10], [1,2,20], [2,3,30], [0,3,40]], and k = 2.

  1. Possible Highway Choices: We can pick up to 2 highways, but they must not overlap in cities.
  2. Evaluate Combinations:
    • Pick [0,1,10] and [2,3,30]: Total cost = 10 + 30 = 40 (cities 0,1,2,3 all unique)
    • Pick [1,2,20] and [0,3,40]: Overlap on cities 0,1,2,3 (cannot pick both)
    • Pick [0,3,40] alone: Total cost = 40
    • Pick [2,3,30] alone: Total cost = 30
    • Pick [1,2,20] alone: Total cost = 20
  3. Best Solution: The best is picking [0,1,10] and [2,3,30], total = 40. Picking [0,3,40] alone is also 40, so both are optimal.

The DP will try all possible combinations and find the maximum sum with at most 4 cities (2 highways).

Time and Space Complexity

Brute-force Approach: Tries all subsets of highways, which is O(2^m), where m is the number of highways. For each subset, we check for city overlaps (O(k)), so total is exponential and impractical for large m.

Bitmask DP Approach:

  • There are O(2^n) possible city masks (since each city can be used or not).
  • For each mask, we check all O(m) highways for transitions.
  • Total time complexity is O(m * 2^n).
  • Space complexity is O(2^n) for the DP table.

This is feasible for n up to about 16-20.

Summary

The problem is a variant of the maximum weight matching in a general graph with an upper limit on the number of selected edges. For small graphs, bitmask dynamic programming provides an exact and optimal solution by efficiently exploring all possible ways to select up to k non-overlapping highways. The key insight is to represent the set of used cities as a bitmask and use DP to maximize the total cost. For larger graphs, more advanced matching algorithms or greedy approximations may be considered, but for small n, bitmask DP is both elegant and effective.

Code Implementation

def maximumCostTripWithKHighways(n, highways, k):
    # DP: dp[mask] = max cost for using the set of cities in mask
    dp = [0] * (1 << n)
    for mask in range(1 << n):
        # Count how many cities are used in this mask
        used = bin(mask).count('1')
        if used % 2 != 0 or used // 2 > k:
            continue  # must be even number of cities, at most 2*k
        for u, v, cost in highways:
            if not (mask & (1 << u)) and not (mask & (1 << v)):
                new_mask = mask | (1 << u) | (1 << v)
                if dp[new_mask] < dp[mask] + cost:
                    dp[new_mask] = dp[mask] + cost
    # Get the answer among all masks with at most 2*k cities
    ans = 0
    for mask in range(1 << n):
        if bin(mask).count('1') // 2 <= k:
            ans = max(ans, dp[mask])
    return ans
      
int maximumCostTripWithKHighways(int n, vector<vector<int>>& highways, int k) {
    int N = 1 << n;
    vector<int> dp(N, 0);
    for (int mask = 0; mask < N; ++mask) {
        int used = __builtin_popcount(mask);
        if (used % 2 != 0 || used / 2 > k) continue;
        for (auto& hw : highways) {
            int u = hw[0], v = hw[1], cost = hw[2];
            if (((mask >> u) & 1) == 0 && ((mask >> v) & 1) == 0) {
                int new_mask = mask | (1 << u) | (1 << v);
                if (dp[new_mask] < dp[mask] + cost)
                    dp[new_mask] = dp[mask] + cost;
            }
        }
    }
    int ans = 0;
    for (int mask = 0; mask < N; ++mask) {
        if (__builtin_popcount(mask) / 2 <= k)
            ans = max(ans, dp[mask]);
    }
    return ans;
}
      
public int maximumCostTripWithKHighways(int n, int[][] highways, int k) {
    int N = 1 << n;
    int[] dp = new int[N];
    for (int mask = 0; mask < N; ++mask) {
        int used = Integer.bitCount(mask);
        if (used % 2 != 0 || used / 2 > k) continue;
        for (int[] hw : highways) {
            int u = hw[0], v = hw[1], cost = hw[2];
            if (((mask >> u) & 1) == 0 && ((mask >> v) & 1) == 0) {
                int new_mask = mask | (1 << u) | (1 << v);
                if (dp[new_mask] < dp[mask] + cost)
                    dp[new_mask] = dp[mask] + cost;
            }
        }
    }
    int ans = 0;
    for (int mask = 0; mask < N; ++mask) {
        if (Integer.bitCount(mask) / 2 <= k)
            ans = Math.max(ans, dp[mask]);
    }
    return ans;
}
      
function maximumCostTripWithKHighways(n, highways, k) {
    const N = 1 << n;
    const dp = new Array(N).fill(0);
    for (let mask = 0; mask < N; ++mask) {
        let used = 0;
        for (let i = 0; i < n; ++i)
            if ((mask >> i) & 1) used++;
        if (used % 2 !== 0 || Math.floor(used / 2) > k) continue;
        for (const [u, v, cost] of highways) {
            if (((mask >> u) & 1) === 0 && ((mask >> v) & 1) === 0) {
                const new_mask = mask | (1 << u) | (1 << v);
                if (dp[new_mask] < dp[mask] + cost)
                    dp[new_mask] = dp[mask] + cost;
            }
        }
    }
    let ans = 0;
    for (let mask = 0; mask < N; ++mask) {
        let used = 0;
        for (let i = 0; i < n; ++i)
            if ((mask >> i) & 1) used++;
        if (Math.floor(used / 2) <= k)
            ans = Math.max(ans, dp[mask]);
    }
    return ans;
}