3724. Minimum Operations to Transform Array
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]by1. - Decrease
nums1[i]by1. - Append a copy of
nums1[i]to the end ofnums1.
Each of these counts as a single operation.
Return the minimum total number of operations needed.
A few key points to understand the problem:
-
Since
nums2has one more element thannums1, you must use the append operation at least once to make the lengths match. The appended value is a copy of some existing elementnums1[i]. -
The first
npositions ofnums1must end up equal to the firstnelements ofnums2. Changingnums1[i]tonums2[i]costs exactly|nums1[i] - nums2[i]|operations (each step of+1or-1is one operation). -
The appended element must eventually equal
nums2[n](the last element ofnums2). You can be clever here: while adjustingnums1[i]from its original value towardnums2[i], if the valuenums2[n]lies betweennums1[i]andnums2[i], you can append a copy at the exact moment the element passes throughnums2[n]— costing only1extra operation for the append itself. -
If no element's adjustment path passes through
nums2[n], you must pay extra: the smallest gapdbetweennums2[n]and any value on the boundary of some element's path (either its starting valuenums1[i]or its targetnums2[i]), plus the1operation for the append.
Example:
Suppose nums1 = [3] and nums2 = [5, 4].
- Adjusting
3 → 5costs2operations. Along the way, the value passes through4, so when it reaches4, append a copy (cost1), then continue to5(total:2 + 1 = 3operations).
The answer is the minimum number of such operations over all possible strategies.
How We Pick the Algorithm
Why Greedy Algorithms?
This problem maps to Greedy Algorithms through a short path in the full flowchart.
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 FlowchartIntuition
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)+ donly if no element's path already coversnums2[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 = 1accounts for the one append operation we must always perform (sincenums2is one element longer thannums1).okis a flag indicating whether some element's adjustment path passes throughnums2[-1](the target value of the appended element).dtracks 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 tox = 5, y = 3. Thenans = 1 + 2 = 3,d = min(inf, |5-4|, |3-4|) = 1, and since3 <= 4 <= 5,ok = True. okisTrue, so no extra cost. Return3.
Complexity Analysis.
- Time complexity:
O(n)— a single pass through thenpaired 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? No —okstaysFalse.
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? No —okstaysFalse.
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:
| Operation | State of nums1 | Cost |
|---|---|---|
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], cost2, and2 <= 3 <= 4→ok = True. - Pair
(8, 7)→ interval[7, 8], cost1.
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
391class 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}
381class 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};
431/**
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}
60Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the array. The code iterates throughnums1andnums2exactly once viazip(nums1, nums2), and each iteration performs only constant-time operations (comparisons, swaps, arithmetic updates ofansandd, and the boolean check forok). -
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 RoadmapWhich type of traversal does breadth first search do?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!