# Merge Operations for Minimum Travel Time
**Difficulty:** HARD
[External](https://leetcode.com/problems/merge-operations-for-minimum-travel-time)
Canonical: https://scaleengineer.com/dsa/problems/merge-operations-for-minimum-travel-time
**Patterns:** [Dynamic Programming](https://scaleengineer.com/dsa/patterns/dynamic-programming), [Prefix Sum](https://scaleengineer.com/dsa/patterns/prefix-sum)
**Data structures:** Array
---
## Problem
You are given a straight road of length `l` km, an integer `n`, an integer `k`**,** and **two** integer arrays, `position` and `time`, each of length `n`.

The array `position` lists the positions (in km) of signs in **strictly** increasing order (with `position[0] = 0` and `position[n - 1] = l`).

Each `time[i]` represents the time (in minutes) required to travel 1 km between `position[i]` and `position[i + 1]`.

You **must** perform **exactly** `k` merge operations. In one merge, you can choose any **two** adjacent signs at indices `i` and `i + 1` (with `i > 0` and `i + 1 < n`) and:

* Update the sign at index `i + 1` so that its time becomes `time[i] + time[i + 1]`.
* Remove the sign at index `i`.

Return the **minimum** **total** **travel time** (in minutes) to travel from 0 to `l` after **exactly** `k` merges.

**Example 1:**

**Input:** l = 10, n = 4, k = 1, position = \[0,3,8,10\], time = \[5,8,3,6\]

**Output:** 62

**Explanation:**

* Merge the signs at indices 1 and 2\. Remove the sign at index 1, and change the time at index 2 to `8 + 3 = 11`.
* After the merge:  
  * `position` array: `[0, 8, 10]`
  * `time` array: `[5, 11, 6]`
* | Segment | Distance (km) | Time per km (min) | Segment Travel Time (min) |
| ------- | ------------- | ----------------- | ------------------------- |
| 0 → 8   | 8             | 5                 | 8 × 5 = 40                |
| 8 → 10  | 2             | 11                | 2 × 11 = 22               |
* Total Travel Time: `40 + 22 = 62`, which is the minimum possible time after exactly 1 merge.

**Example 2:**

**Input:** l = 5, n = 5, k = 1, position = \[0,1,2,3,5\], time = \[8,3,9,3,3\]

**Output:** 34

**Explanation:**

* Merge the signs at indices 1 and 2\. Remove the sign at index 1, and change the time at index 2 to `3 + 9 = 12`.
* After the merge:  
  * `position` array: `[0, 2, 3, 5]`
  * `time` array: `[8, 12, 3, 3]`
* | Segment | Distance (km) | Time per km (min) | Segment Travel Time (min) |
| ------- | ------------- | ----------------- | ------------------------- |
| 0 → 2   | 2             | 8                 | 2 × 8 = 16                |
| 2 → 3   | 1             | 12                | 1 × 12 = 12               |
| 3 → 5   | 2             | 3                 | 2 × 3 = 6                 |
* Total Travel Time: `16 + 12 + 6 = 34`**,** which is the minimum possible time after exactly 1 merge.

**Constraints:**

* `1 <= l <= 105`
* `2 <= n <= min(l + 1, 50)`
* `0 <= k <= min(n - 2, 10)`
* `position.length == n`
* `position[0] = 0` and `position[n - 1] = l`
* `position` is sorted in strictly increasing order.
* `time.length == n`
* `1 <= time[i] <= 100​`
* `1 <= sum(time) <= 100`​​​​​​

# Approaches
## Brute-Force Recursion
A brute-force approach involves exploring all possible sequences of exactly `k` merge operations. We can define a recursive function that tries every possible merge at each step.
**Time:** O((n-2)!/(n-2-k)! * n * k). The number of ways to choose k ordered merges is P(n-2, k). Each recursive call involves creating new arrays, taking O(n) time. The recursion depth is k. This is computationally infeasible for the given constraints. · **Space:** O(n*k) due to the recursion stack depth (`k`) and storing new copies of arrays (`n`) at each level.
**Pros:** Simple to understand conceptually.; Correctly explores all possibilities.
**Cons:** Extremely inefficient.; Will time out for the given constraints.
### Explanation
The state of our recursion can be defined by the current `position` and `time` arrays, and the number of merges `k` remaining. The function would look like `solve(current_positions, current_times, k_left)`.

*   **Base Case:** If `k_left` is 0, we calculate the total travel time based on the current `positions` and `times` arrays and return it.
*   **Recursive Step:** We iterate through all valid merge operations. A merge can be performed on adjacent signs at indices `i` and `i+1` for `0 < i < n-1` (where `n` is the current number of signs). For each possible merge:
    1.  We simulate the merge: create new `positions'` and `times'` arrays reflecting the changes.
    2.  We make a recursive call: `solve(positions', times', k_left - 1)`.
    3.  We keep track of the minimum travel time returned by all recursive calls.

This approach is exhaustive and guaranteed to find the minimum time, but it's highly inefficient because it recomputes solutions for the same subproblems and explores a very large number of paths.

```java
// This is a conceptual representation. A full implementation would be very verbose.
class Solution {
    public long minimumTime(int l, int n, int k, int[] position, int[] time) {
        // Convert arrays to lists for easier manipulation
        List<Integer> posList = new ArrayList<>();
        for (int p : position) posList.add(p);
        List<Integer> timeList = new ArrayList<>();
        for (int t : time) timeList.add(t);
        return solve(posList, timeList, k);
    }

    private long solve(List<Integer> pos, List<Integer> time, int k) {
        if (k == 0) {
            return calculateTotalTime(pos, time);
        }

        long minTime = Long.MAX_VALUE;
        // Iterate through all possible merges
        // Merge signs at i and i+1, which removes sign at i
        // Valid i is from 1 to current_n - 2
        for (int i = 1; i < pos.size() - 1; i++) {
            List<Integer> nextPos = new ArrayList<>(pos);
            List<Integer> nextTime = new ArrayList<>(time);

            // Perform the merge
            int removedTime = nextTime.get(i);
            nextPos.remove(i);
            nextTime.remove(i);
            nextTime.set(i, nextTime.get(i) + removedTime);

            minTime = Math.min(minTime, solve(nextPos, nextTime, k - 1));
        }

        return minTime;
    }

    private long calculateTotalTime(List<Integer> pos, List<Integer> time) {
        long totalTime = 0;
        for (int i = 0; i < pos.size() - 1; i++) {
            long distance = pos.get(i + 1) - pos.get(i);
            totalTime += distance * time.get(i);
        }
        return totalTime;
    }
}
```
### Algorithm
["Define a recursive function `solve(positions, times, k)`.","If `k` is 0, calculate and return the total travel time for the current configuration.","Initialize `min_time` to infinity.","Iterate through all possible merge operations on the current set of signs.","For each merge, create new `positions'` and `times'` arrays.","Call `solve(positions', times', k-1)` and update `min_time` with the minimum value found.","Return `min_time`."]

## Dynamic Programming
A more efficient approach uses dynamic programming. The problem can be modeled as partitioning the original signs into groups. Each group consists of one kept sign followed by zero or more removed signs. The total number of merges determines the number of removed signs. The key challenge is that the travel time of a segment depends on the time property of its starting sign, which in turn depends on the signs removed before it.
**Time:** O(n^3 * k). The state is defined by `i`, `j`, and `p`. The transition involves iterating over `m`. The loops run up to `n` for `i`, `p`, `m` and up to `k` for `j`. Specifically, the loops can be structured as `i` (1 to n-1), `p` (1 to i-1), `m` (0 to p-1), and `j_prev` (0 to k), leading to O(n^3 * k) complexity. · **Space:** O(n^2 * k) for the DP table `dp[i][j][p]` where `i` and `p` go up to `n`, and `j` goes up to `k`.
**Pros:** Correctly models the complex dependencies between merge operations.; Efficient enough to pass within the given constraints.; Guaranteed to find the optimal solution.
**Cons:** The DP state and transitions are complex to formulate and implement correctly.; High memory usage due to the 3D DP table.
### Explanation
We can define a DP state that captures all necessary information to build the solution incrementally.

**DP State:**
`dp[i][j][p]` = The minimum total travel time for the road up to `position[i]`, given that:
1.  Sign `s_i` is kept.
2.  A total of `j` merges have been performed on signs `s_1, ..., s_{i-1}`.
3.  The sign kept immediately before `s_i` was `s_p`.

**DP Transition:**
To compute `dp[i][j][p]`, we consider that signs `s_{p+1}, ..., s_{i-1}` must have been removed. This accounts for `i - p - 1` merges. The remaining `j - (i - p - 1)` merges must have occurred before sign `s_p`. The state before `s_p` is characterized by `dp[p][j - (i - p - 1)][m]`, where `s_m` was the sign kept before `s_p`.

The travel time for the new segment from `s_p` to `s_i` is `(position[i] - position[p]) * T_p`, where `T_p` is the time property of sign `s_p`. This property is the sum of original `time` values for all signs from `s_{m+1}` to `s_p`.

The recurrence is:
`dp[i][j][p] = min_{0 <= m < p} { dp[p][j - (i - p - 1)][m] + (position[i] - position[p]) * sum(time[l] for l from m+1 to p) }`

We can precompute prefix sums of the `time` array to calculate the sum in O(1).

**Base Cases:**
The first segment always starts at `s_0`. If `s_i` is the next kept sign after `s_0`, then `p=0`. This requires `i-1` merges. The travel time is `(position[i] - position[0]) * time[0]`. So, `dp[i][i-1][0] = (position[i] - position[0]) * time[0]` for `1 <= i < n` as long as `i-1 <= k`.

**Final Answer:**
The minimum total time after exactly `k` merges is the minimum of `dp[n-1][k][p]` over all possible penultimate kept signs `s_p` (`0 <= p < n-1`).

```java
import java.util.Arrays;

class Solution {
    public long minimumTime(int l, int n, int k, int[] position, int[] time) {
        long[][][] dp = new long[n][k + 1][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= k; j++) {
                Arrays.fill(dp[i][j], -1);
            }
        }

        long[] timeSum = new long[n + 1];
        for (int i = 0; i < n; i++) {
            timeSum[i + 1] = timeSum[i] + time[i];
        }

        // Base cases: s_0 is the first kept sign
        for (int i = 1; i < n; i++) {
            int merges = i - 1;
            if (merges <= k) {
                dp[i][merges][0] = (long)(position[i] - position[0]) * time[0];
            }
        }

        // DP transitions
        for (int i = 2; i < n; i++) { // current kept sign
            for (int p = 1; p < i; p++) { // previous kept sign
                for (int m = 0; m < p; m++) { // prev-prev kept sign
                    int mergesBetween = i - p - 1;
                    long segmentTime = (long)(position[i] - position[p]) * (timeSum[p + 1] - timeSum[m + 1]);
                    
                    for (int prevMerges = 0; prevMerges <= k - mergesBetween; prevMerges++) {
                        if (dp[p][prevMerges][m] != -1) {
                            int currentMerges = prevMerges + mergesBetween;
                            long newTotalTime = dp[p][prevMerges][m] + segmentTime;
                            if (dp[i][currentMerges][p] == -1 || newTotalTime < dp[i][currentMerges][p]) {
                                dp[i][currentMerges][p] = newTotalTime;
                            }
                        }
                    }
                }
            }
        }

        long minTime = Long.MAX_VALUE;
        for (int p = 0; p < n - 1; p++) {
            if (dp[n - 1][k][p] != -1) {
                minTime = Math.min(minTime, dp[n - 1][k][p]);
            }
        }

        return minTime;
    }
}
```
### Algorithm
["First, understand that the problem is equivalent to partitioning the signs `s_0, ..., s_{n-1}` into `n-k` groups, where each group starts with a kept sign and is followed by removed signs.","The travel time of a segment between two kept signs `s_p` and `s_i` depends on `s_p`'s time property, which in turn depends on the sign `s_m` kept before `s_p`.","Define a 3D DP table `dp[i][j][p]` to store the minimum travel time ending at `pos[i]`, with `s_i` kept, `j` merges used, and `s_p` as the previously kept sign.","Precompute prefix sums of the `time` array for efficient calculation of time properties.","Initialize the DP table with a value indicating infinity (e.g., -1 or `Long.MAX_VALUE`).","Set up base cases for when `s_0` is the first kept sign.","Iterate through `i` (current kept sign), `p` (previous kept sign), `m` (pre-previous kept sign), and `j` (merges) to fill the DP table using the transition formula.","The final answer is the minimum value in `dp[n-1][k][p]` for all possible `p`."]
