Facebook Pixel

3724. Minimum Operations to Transform Array

MediumGreedyArray
LeetCode ↗

Problem Description

You are given two integer arrays: nums1 with length n and nums2 with length n + 1. Notice that nums2 is exactly one element longer than nums1.

Your goal is to transform nums1 so that it becomes identical to nums2, using as few operations as possible.

In each operation, you pick an index i of nums1 and do one of the following:

  • Increase nums1[i] by 1.
  • Decrease nums1[i] by 1.
  • Append a copy of nums1[i] to the end of nums1.

Each of these counts as a single operation.

Return the minimum total number of operations needed.

A few key points to understand the problem:

  1. Since nums2 has one more element than nums1, you must use the append operation at least once to make the lengths match. The appended value is a copy of some existing element nums1[i].

  2. The first n positions of nums1 must end up equal to the first n elements of nums2. Changing nums1[i] to nums2[i] costs exactly |nums1[i] - nums2[i]| operations (each step of +1 or -1 is one operation).

  3. The appended element must eventually equal nums2[n] (the last element of nums2). You can be clever here: while adjusting nums1[i] from its original value toward nums2[i], if the value nums2[n] lies between nums1[i] and nums2[i], you can append a copy at the exact moment the element passes through nums2[n] — costing only 1 extra operation for the append itself.

  4. If no element's adjustment path passes through nums2[n], you must pay extra: the smallest gap d between nums2[n] and any value on the boundary of some element's path (either its starting value nums1[i] or its target nums2[i]), plus the 1 operation for the append.

Example:

Suppose nums1 = [3] and nums2 = [5, 4].

  • Adjusting 3 → 5 costs 2 operations. Along the way, the value passes through 4, so when it reaches 4, append a copy (cost 1), then continue to 5 (total: 2 + 1 = 3 operations).

The answer is the minimum number of such operations over all possible strategies.

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

How We Pick the Algorithm

Why Greedy Algorithms?

This problem maps to Greedy Algorithms through a short path in the full flowchart.

Tree orgraph?noMax/minwithgreedyyesGreedyAlgorithms

The optimal append position is found by checking each element's adjustment range to see if the target value lies on its path.

Open in Flowchart

Intuition

Let's start by figuring out what costs are unavoidable, and then think about where we have freedom to save operations.

The unavoidable part. The first n elements of nums1 must match the first n elements of nums2. The only way to change a value is by +1 or -1 steps, so each pair costs exactly |nums1[i] - nums2[i]| operations — there's no way around it. Also, since nums2 is one element longer, we must perform at least one append, which costs 1 operation. So a hard lower bound on the answer is:

sum(|nums1[i] - nums2[i]|) + 1

The interesting part: where does the appended element come from? The appended value is a snapshot of some nums1[i] at the moment we copy it, and it must end up equal to nums2[n] (the last element of nums2). The key insight is that elements don't jump — they move one step at a time, so as nums1[i] travels from its original value to nums2[i], it passes through every integer in between.

This gives us a free ride: if nums2[n] lies inside the interval [min(nums1[i], nums2[i]), max(nums1[i], nums2[i])] for any index i, we can pause that element's journey exactly when it equals nums2[n], append it (cost 1, already counted), and continue. The appended copy is already correct — zero extra cost beyond the lower bound.

When no path passes through nums2[n]. If nums2[n] falls outside every element's travel interval, we have to make a detour. Take some element, walk it to nums2[n], append, then walk it back to where it needs to go. The detour costs 2 × (distance overshot) — but wait, we can be smarter: we only need to move the element to nums2[n] before appending or after copying, and the cheapest detour is measured from whichever endpoint of an element's interval is closest to nums2[n]. Actually, thinking carefully: we can append a copy at any point during the journey, then keep adjusting the appended element separately. So the extra cost is just the minimal distance:

d = min over all i of min(|nums1[i] - nums2[n]|, |nums2[i] - nums2[n]|)

We move the appended copy from the nearest reachable value to nums2[n], paying d extra operations.

Putting it together. The answer is:

  • sum of |nums1[i] - nums2[i]| (mandatory adjustments)
  • + 1 (mandatory append)
  • + d only if no element's path already covers nums2[n].

This explains the greedy one-pass structure: as we zip through the pairs, we accumulate the differences, check the "covers nums2[n]" condition (y <= nums2[-1] <= x after ordering the pair), and track the fallback distance d in case the condition never holds.

Pattern Learn more about Greedy patterns.

Solution Approach

The implementation is a single greedy pass over the paired elements of nums1 and nums2, tracking three things along the way: the accumulated adjustment cost, whether a "free" append point exists, and the cheapest fallback distance.

Step 1: Initialize the answer with the mandatory append.

ans = 1
ok = False
d = inf
  • ans = 1 accounts for the one append operation we must always perform (since nums2 is one element longer than nums1).
  • ok is a flag indicating whether some element's adjustment path passes through nums2[-1] (the target value of the appended element).
  • d tracks the minimum extra distance needed if no such path exists, initialized to infinity.

Step 2: Iterate over each pair (nums1[i], nums2[i]).

for x, y in zip(nums1, nums2):
    if x < y:
        x, y = y, x

We normalize each pair so that x is the larger value and y is the smaller. After the swap, the element's travel interval is simply [y, x], which simplifies the checks below.

Step 3: Accumulate the mandatory adjustment cost.

    ans += x - y

Since x >= y after normalization, x - y equals |nums1[i] - nums2[i]| — the unavoidable cost of adjusting nums1[i] to nums2[i].

Step 4: Track the fallback distance.

    d = min(d, abs(x - nums2[-1]), abs(y - nums2[-1]))

If we end up needing a detour, the cheapest one starts from whichever interval endpoint (the original value or the target value) is closest to nums2[-1]. We keep the global minimum of these distances across all indices.

Step 5: Check for a free append point.

    ok = ok or y <= nums2[-1] <= x

If nums2[-1] lies within the interval [y, x], then while adjusting this element step by step, its value will pass through nums2[-1] exactly. At that moment we append a copy — the copy is already correct, costing nothing beyond the 1 already counted. Once ok becomes True, it stays True.

Step 6: Pay the detour if necessary.

if not ok:
    ans += d
return ans

If no element's path covers nums2[-1], we append a copy from the nearest reachable value and adjust the copy itself by d steps to reach nums2[-1].

Worked trace. Take nums1 = [3], nums2 = [5, 4]:

  • Start: ans = 1, ok = False, d = inf.
  • Pair (3, 5) → normalized to x = 5, y = 3. Then ans = 1 + 2 = 3, d = min(inf, |5-4|, |3-4|) = 1, and since 3 <= 4 <= 5, ok = True.
  • ok is True, so no extra cost. Return 3.

Complexity Analysis.

  • Time complexity: O(n) — a single pass through the n paired elements, with constant work per pair.
  • Space complexity: O(1) — only a fixed number of scalar variables (ans, ok, d) are used, regardless of input size.

Example Walkthrough

Let's trace the algorithm on nums1 = [2, 8] and nums2 = [4, 7, 10], where the appended element must end up equal to nums2[-1] = 10.

Initialization:

ans = 1      # mandatory append operation
ok  = False  # no free append point found yet
d   = inf    # cheapest fallback distance

Iteration 1 — pair (2, 4):

  • Normalize: since 2 < 4, swap → x = 4, y = 2. The travel interval is [2, 4].
  • Accumulate adjustment cost: ans = 1 + (4 - 2) = 3.
  • Update fallback distance: d = min(inf, |4 - 10|, |2 - 10|) = min(inf, 6, 8) = 6.
  • Free append check: is 2 <= 10 <= 4? Nook stays False.

Iteration 2 — pair (8, 7):

  • Normalize: 8 >= 7, no swap → x = 8, y = 7. The travel interval is [7, 8].
  • Accumulate adjustment cost: ans = 3 + (8 - 7) = 4.
  • Update fallback distance: d = min(6, |8 - 10|, |7 - 10|) = min(6, 2, 3) = 2.
  • Free append check: is 7 <= 10 <= 8? Nook stays False.

Final step:

No element's path passes through 10, so we must pay the detour: ans = 4 + d = 4 + 2 = 6.

Why 6 is correct — an explicit strategy:

OperationState of nums1Cost
Increase 2 → 3 → 4[4, 8]2
Append a copy of 8[4, 8, 8]1
Increase the copy 8 → 9 → 10[4, 8, 10]2
Decrease 8 → 7[4, 7, 10]1

Total: 2 + 1 + 2 + 1 = 6 ✓ — matching nums2 = [4, 7, 10] exactly. The detour of d = 2 came from the endpoint 8, the value closest to the target 10 among all interval boundaries.

Contrast — when the append is free:

Now change the last element so that nums2 = [4, 7, 3]. Re-running the pass:

  • Pair (2, 4) → interval [2, 4], cost 2, and 2 <= 3 <= 4ok = True.
  • Pair (8, 7) → interval [7, 8], cost 1.

Since ok is True, the answer is just 1 + 2 + 1 = 4. Concretely: increase 2 → 3 (1 op), append the copy 3 at that exact moment (1 op), continue 3 → 4 (1 op), then decrease 8 → 7 (1 op) — four operations, with the appended element landing on 3 "in passing" at zero extra cost.

These two runs highlight the core dichotomy of the greedy pass: when nums2[-1] is covered by some adjustment interval, the lower bound sum(|nums1[i] - nums2[i]|) + 1 is achieved exactly; otherwise the minimal endpoint gap d is the unavoidable surcharge.

Solution Implementation

1from math import inf
2from typing import List
3
4
5class Solution:
6    def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
7        # The value that at least one interval [low, high] must cover
8        target = nums2[-1]
9
10        # Start with 1 operation (the baseline operation counted by the algorithm)
11        total_ops = 1
12
13        # Whether some pair (low, high) already covers the target value
14        target_covered = False
15
16        # The minimum extra distance needed to extend some pair to reach the target
17        min_gap = inf
18
19        for high, low in zip(nums1, nums2):
20            # Normalize each pair so that `high` is the larger value
21            # and `low` is the smaller one
22            if high < low:
23                high, low = low, high
24
25            # The gap inside the pair always costs (high - low) operations
26            total_ops += high - low
27
28            # Track the cheapest way to stretch any pair's endpoint to the target
29            min_gap = min(min_gap, abs(high - target), abs(low - target))
30
31            # Check whether this pair's interval [low, high] already covers the target
32            target_covered = target_covered or (low <= target <= high)
33
34        # If no pair covers the target, pay the cheapest extension cost once
35        if not target_covered:
36            total_ops += min_gap
37
38        return total_ops
39
1class Solution {
2    public long minOperations(int[] nums1, int[] nums2) {
3        // The total number of operations; initialized to 1
4        long totalOperations = 1;
5        int length = nums1.length;
6        // The target value located at index 'length' of nums2
7        int target = nums2[length];
8        // Flag indicating whether the target falls within any [low, high] interval
9        boolean isTargetCovered = false;
10        // The minimum distance from the target to any interval boundary
11        int minDistance = 1 << 30;
12
13        for (int i = 0; i < length; ++i) {
14            // Determine the upper and lower bounds at the current index
15            int high = Math.max(nums1[i], nums2[i]);
16            int low = Math.min(nums1[i], nums2[i]);
17
18            // Accumulate the gap between the two bounds
19            totalOperations += high - low;
20
21            // Update the minimum distance from the target to either bound
22            minDistance = Math.min(minDistance,
23                    Math.min(Math.abs(high - target), Math.abs(low - target)));
24
25            // Check whether the target lies within the interval [low, high]
26            isTargetCovered = isTargetCovered || (target >= low && target <= high);
27        }
28
29        // If the target is not covered by any interval,
30        // add the minimum distance as extra operations
31        if (!isTargetCovered) {
32            totalOperations += minDistance;
33        }
34
35        return totalOperations;
36    }
37}
38
1class Solution {
2public:
3    long long minOperations(vector<int>& nums1, vector<int>& nums2) {
4        // Total number of operations; starts at 1 for the mandatory base operation.
5        long long totalOps = 1;
6
7        // Number of paired elements to process (nums2 has one extra element at index n).
8        int n = nums1.size();
9
10        // Flag indicating whether the target value falls inside at least one [low, high] interval.
11        bool isCovered = false;
12
13        // Minimum extra distance required if the target is not covered by any interval.
14        int minExtraDist = 1 << 30;
15
16        // The last element of nums2 serves as the target value all pairs relate to.
17        int target = nums2[n];
18
19        for (int i = 0; i < n; ++i) {
20            // For each index, determine the upper and lower bounds of the pair.
21            int high = max(nums1[i], nums2[i]);
22            int low = min(nums1[i], nums2[i]);
23
24            // The cost to align this pair is the gap between its two values.
25            totalOps += high - low;
26
27            // Track the closest distance from either bound to the target,
28            // in case no interval contains the target.
29            minExtraDist = min(minExtraDist, min(abs(high - target), abs(low - target)));
30
31            // Check whether the target lies within the current interval [low, high].
32            isCovered = isCovered || (target >= low && target <= high);
33        }
34
35        // If no interval covers the target, pay the minimal extra distance to reach it.
36        if (!isCovered) {
37            totalOps += minExtraDist;
38        }
39
40        return totalOps;
41    }
42};
43
1/**
2 * Calculates the minimum number of operations required.
3 *
4 * For each index i, the answer accumulates the gap between the larger and
5 * smaller of nums1[i] and nums2[i]. Additionally, the target value nums2[n]
6 * must be coverable by at least one interval [min, max]; if it is not, the
7 * smallest extra adjustment needed to reach it is added to the answer.
8 *
9 * @param nums1 - The first array of numbers
10 * @param nums2 - The second array of numbers (its element at index n is the target value)
11 * @returns The minimum number of operations
12 */
13function minOperations(nums1: number[], nums2: number[]): number {
14    // Length of the first array; nums2[n] serves as the target value
15    const n: number = nums1.length;
16
17    // The target value that should fall within at least one [min, max] interval
18    const target: number = nums2[n];
19
20    // Start the answer at 1 (base operation count)
21    let ans: number = 1;
22
23    // Flag indicating whether the target lies inside some interval [min, max]
24    let covered: boolean = false;
25
26    // Minimum extra distance needed to reach the target if it is never covered
27    let minDistance: number = 1 << 30;
28
29    // Iterate over every paired position of the two arrays
30    for (let i = 0; i < n; ++i) {
31        // The larger of the two values at index i
32        const maxVal: number = Math.max(nums1[i], nums2[i]);
33
34        // The smaller of the two values at index i
35        const minVal: number = Math.min(nums1[i], nums2[i]);
36
37        // Accumulate the gap between the two values
38        ans += maxVal - minVal;
39
40        // Track the closest distance from either endpoint to the target
41        minDistance = Math.min(
42            minDistance,
43            Math.abs(maxVal - target),
44            Math.abs(minVal - target)
45        );
46
47        // Check whether the target falls inside the interval [minVal, maxVal]
48        if (target >= minVal && target <= maxVal) {
49            covered = true;
50        }
51    }
52
53    // If the target is never covered by any interval, pay the extra cost
54    if (!covered) {
55        ans += minDistance;
56    }
57
58    return ans;
59}
60

Time and Space Complexity

  • Time Complexity: O(n), where n is the length of the array. The code iterates through nums1 and nums2 exactly once via zip(nums1, nums2), and each iteration performs only constant-time operations (comparisons, swaps, arithmetic updates of ans and d, and the boolean check for ok).

  • Space Complexity: O(1). The algorithm only uses a fixed number of auxiliary variables (ans, ok, d, x, y), regardless of the input size, so the extra space required is constant.

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

Common Pitfalls

Pitfall 1: Forgetting to normalize the pair before the interval check

The most frequent mistake is testing whether the target lies "between" the two values without first ordering them:

# WRONG: assumes nums1[i] <= nums2[i]
for x, y in zip(nums1, nums2):
    ans += abs(x - y)
    ok = ok or x <= nums2[-1] <= y   # fails whenever x > y

If nums1[i] = 7 and nums2[i] = 2, the element travels through the interval [2, 7]. With target = 5, the broken check 7 <= 5 <= 2 is False, so the code wrongly pays an extra detour of min_gap, inflating the answer.

Fix: Swap the pair so the check always operates on a valid interval [low, high]:

if high < low:
    high, low = low, high
ok = ok or (low <= target <= high)

Equivalently, min(x, y) <= target <= max(x, y) works without swapping.


Pitfall 2: Using strict inequalities for the containment check

# WRONG: misses the case where target equals an endpoint
ok = ok or (low < target < high)

If nums1[i] == nums2[-1] (the element starts at the target value) or nums2[i] == nums2[-1] (it ends there), you can append a copy at zero extra adjustment cost. Strict inequalities reject these boundary cases. The code still produces the right answer here only by luck — min_gap becomes 0 — but mixing the two mechanisms makes the logic fragile and breaks variants of the solution that skip the min_gap fallback when intervals are degenerate (low == high).

Fix: Always use inclusive bounds: low <= target <= high.


Pitfall 3: Computing the fallback distance from only one endpoint

# WRONG: only considers the target value of the pair
min_gap = min(min_gap, abs(low - target))

The detour can launch from either end of an element's journey: append a copy before you start moving the element (distance from nums1[i]), or after it reaches its destination (distance from nums2[i]). Considering only one endpoint can overpay. For example, nums1 = [10], nums2 = [2, 9]: the correct detour distance is |10 - 9| = 1 (append immediately, then adjust the copy by 1), not |2 - 9| = 7.

Fix: Take the minimum over both endpoints:

min_gap = min(min_gap, abs(high - target), abs(low - target))

Pitfall 4: Forgetting the mandatory +1 for the append

# WRONG: initializes the counter at zero
total_ops = 0

Even in the best case — where an element's path passes exactly through nums2[-1] — the append itself is still one operation. Initializing total_ops = 0 undercounts every answer by exactly 1, which often slips past hand-checked examples if the trace is done sloppily.

Fix: Start with total_ops = 1, or add 1 at the end before returning.


Pitfall 5: Adding the detour cost per element instead of once globally

# WRONG: pays a gap penalty inside the loop
for high, low in ...:
    ...
    if not (low <= target <= high):
        total_ops += min(abs(high - target), abs(low - target))

Only one append happens, so only one detour is ever paid — and only if no interval covers the target. Charging a gap per non-covering element massively overcounts. The decision must be deferred until after the full pass, using the global minimum:

if not target_covered:
    total_ops += min_gap

Pitfall 6: Misaligning the iteration with nums2's extra element

Manually indexing both arrays with the same range is a classic off-by-one trap:

# WRONG: IndexError on nums1, or silently pairs nums1[i] with the wrong element
for i in range(len(nums2)):
    total_ops += abs(nums1[i] - nums2[i])

The last element nums2[n] is not a pairing target — it is the value the appended copy must reach, and it must be excluded from the pairwise adjustment cost. The zip(nums1, nums2) idiom handles this cleanly because it truncates to the shorter array, but anyone rewriting the loop with explicit indices (e.g., in another language) must iterate i over range(n) and treat nums2[n] separately as the target.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which type of traversal does breadth first search do?


Recommended Readings

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

Load More