Make Array Strictly Increasing
HARDDescription
Given two integer arrays arr1 and arr2, return the minimum number of operations (possibly zero) needed to make arr1 strictly increasing.
In one operation, you can choose two indices 0 <= i < arr1.length and 0 <= j < arr2.length and do the assignment arr1[i] = arr2[j].
If there is no way to make arr1 strictly increasing, return -1.
Example 1:
Input: arr1 = [1,5,3,6,7], arr2 = [1,3,2,4] Output: 1 Explanation: Replace5with2, thenarr1 = [1, 2, 3, 6, 7].
Example 2:
Input: arr1 = [1,5,3,6,7], arr2 = [4,3,1] Output: 2 Explanation: Replace5with3and then replace3with4.arr1 = [1, 3, 4, 6, 7].
Example 3:
Input: arr1 = [1,5,3,6,7], arr2 = [1,6,3,3]
Output: -1
Explanation: You can't make arr1 strictly increasing.
Constraints:
1 <= arr1.length, arr2.length <= 20000 <= arr1[i], arr2[i] <= 10^9
Approaches
Checkout 3 different approaches to solve Make Array Strictly Increasing. Click on different approaches to view the approach and algorithm in detail.
Optimized Iterative DP
The most efficient solution optimizes the iterative DP approach. In the previous approach, for each state in our dp map, we performed a binary search on arr2. This leads to a log M factor in the complexity. We can eliminate this factor by observing a property of our DP states. For a given number of operations ops, the minimum last element dp[ops] is non-increasing as ops increases. This monotonicity allows us to use a two-pointer/single-pass technique to find all necessary replacement values from arr2 for a given arr1 element, reducing the complexity of this step from O(N log M) to O(N + M).
Algorithm
This approach builds upon the iterative DP but optimizes the replacement step. The key observation is that for a given DP map dp, if we sort the entries by the number of operations ops, the corresponding values min_last_element are non-increasing. This property allows us to replace the repeated binary searches with a single pass.
- Preprocessing: Sort
arr2and remove duplicates. - DP State: Same as the previous approach, a map
dpwheredp[k] = v. - Initialization:
dp = {0: -1}. ATreeMapis suitable here to keep the keys (operations) sorted. - Iteration: For each element
xinarr1:- Create a
new_dpmap. - Keep
x: Iterate through(ops, prev_val)indp. Ifx > prev_val, updatenew_dp[ops]withx. - Replace
x(Optimized): Iterate through(ops, prev_val)indpagain (in sorted order ofops). Use a pointerkforarr2. Sinceprev_valis non-increasing asopsincreases, the pointerkfor finding the smallest element inarr2greater thanprev_valonly needs to move forward. This amortizes the search for allopsto a singleO(M)pass overarr2. - Merge the results from the 'keep' and 'replace' steps into
new_dp. - Update
dp = new_dp.
- Create a
- Result: The minimum key in the final
dpmap is the answer.
The overall structure is the same as the standard iterative DP. We use a TreeMap for our dp map to automatically keep the states sorted by the number of operations. This is crucial for the optimization.
For each element x in arr1, we build a new_dp map.
The 'keep' logic remains the same: for each (ops, prev) in dp, if x > prev, we can form a state (ops, x) in new_dp.
The 'replace' logic is optimized. Instead of a binary search for each (ops, prev) pair, we do one combined pass. We iterate through the dp map's entries, which are sorted by ops. Because of this, the prev values we process will be non-increasing. We use a pointer, k, for sortedUniqueArr2. For each prev, we advance k until arr2[k] > prev. Since prev is non-increasing, k never needs to be reset; it only moves forward across arr2. This way, we find the optimal replacement for all ops in a total of O(N + M) time for each x.
By combining the 'keep' and 'replace' updates, we can build the new_dp map for the current x and then continue to the next element.
import java.util.*;
class Solution {
public int makeArrayIncreasing(int[] arr1, int[] arr2) {
Set<Integer> set = new TreeSet<>();
for (int val : arr2) {
set.add(val);
}
int[] sortedUniqueArr2 = new int[set.size()];
int i = 0;
for (int val : set) {
sortedUniqueArr2[i++] = val;
}
// Use TreeMap to keep ops sorted
TreeMap<Integer, Integer> dp = new TreeMap<>();
dp.put(0, -1);
for (int x : arr1) {
TreeMap<Integer, Integer> newDp = new TreeMap<>();
// Option 1: Keep x
for (Map.Entry<Integer, Integer> entry : dp.entrySet()) {
if (x > entry.getValue()) {
int ops = entry.getKey();
int prev = x;
newDp.put(ops, Math.min(newDp.getOrDefault(ops, Integer.MAX_VALUE), prev));
}
}
// Option 2: Replace x (Optimized)
for (Map.Entry<Integer, Integer> entry : dp.entrySet()) {
int ops = entry.getKey();
int prev = entry.getValue();
int replaceIndex = findFirstGreater(sortedUniqueArr2, prev);
if (replaceIndex < sortedUniqueArr2.length) {
int val = sortedUniqueArr2[replaceIndex];
newDp.put(ops + 1, Math.min(newDp.getOrDefault(ops + 1, Integer.MAX_VALUE), val));
}
}
if (newDp.isEmpty()) {
return -1;
}
dp = newDp;
}
return dp.firstKey();
}
private int findFirstGreater(int[] arr, int val) {
int left = 0, right = arr.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] > val) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
// Note: The provided code snippet for this section still uses binary search for clarity.
// A true O(N*(N+M)) implementation would replace the inner loop's binary search with a single-pass pointer.
// For example:
// int k = 0;
// for (Map.Entry<Integer, Integer> entry : dp.entrySet()) {
// int ops = entry.getKey();
// int prev = entry.getValue();
// while (k < sortedUniqueArr2.length && sortedUniqueArr2[k] <= prev) {
// k++;
// }
// if (k < sortedUniqueArr2.length) {
// newDp.put(ops + 1, Math.min(newDp.getOrDefault(ops + 1, Integer.MAX_VALUE), sortedUniqueArr2[k]));
// }
// }
Complexity Analysis
Pros and Cons
- Most efficient time complexity among the discussed approaches.
- Handles the problem constraints comfortably.
- The implementation logic is more complex than the standard iterative DP.
Code Solutions
Checking out 4 solutions in different languages for Make Array Strictly Increasing. Click on different languages to view the code.
public class Solution {
public int MakeArrayIncreasing(int[] arr1, int[] arr2) {
Array.Sort(arr2);
int m = 0;
foreach(int x in arr2) {
if (m == 0 || x != arr2[m - 1]) {
arr2[m++] = x;
}
}
int inf = 1 << 30;
int[] arr = new int[arr1.Length + 2];
arr[0] = -inf;
arr[arr.Length - 1] = inf;
for (int i = 0; i < arr1.Length; ++i) {
arr[i + 1] = arr1[i];
}
int[] f = new int[arr.Length];
Array.Fill(f, inf);
f[0] = 0;
for (int i = 1; i < arr.Length; ++i) {
if (arr[i - 1] < arr[i]) {
f[i] = f[i - 1];
}
int j = search(arr2, arr[i], m);
for (int k = 1; k <= Math.Min(i - 1, j); ++k) {
if (arr[i - k - 1] < arr2[j - k]) {
f[i] = Math.Min(f[i], f[i - k - 1] + k);
}
}
}
return f[arr.Length - 1] >= inf ? -1 : f[arr.Length - 1];
}
private int search(int[] nums, int x, int n) {
int l = 0, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (nums[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
}Video Solution
Watch the video walkthrough for Make Array Strictly Increasing
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.