from bisect import bisect_right
class Solution:
def makeArrayIncreasing(self, arr1, arr2):
arr2 = sorted(set(arr2))
dp = {-1: 0} # key: previous value, value: operations count
for num in arr1:
new_dp = {}
for prev in dp:
# Option 1: Keep arr1[i] if it's strictly increasing
if num > prev:
new_dp[num] = min(new_dp.get(num, float('inf')), dp[prev])
# Option 2: Replace with the smallest element in arr2 > prev
idx = bisect_right(arr2, prev)
if idx < len(arr2):
new_dp[arr2[idx]] = min(new_dp.get(arr2[idx], float('inf')), dp[prev] + 1)
dp = new_dp
if not dp:
return -1
return min(dp.values())
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
class Solution {
public:
int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
sort(arr2.begin(), arr2.end());
arr2.erase(unique(arr2.begin(), arr2.end()), arr2.end());
map<int, int> dp;
dp[-1] = 0;
for (int num : arr1) {
map<int, int> new_dp;
for (auto [prev, cnt] : dp) {
// Option 1: Keep arr1[i]
if (num > prev) {
if (!new_dp.count(num) || new_dp[num] > cnt)
new_dp[num] = cnt;
}
// Option 2: Replace with arr2 element
auto it = upper_bound(arr2.begin(), arr2.end(), prev);
if (it != arr2.end()) {
int v = *it;
if (!new_dp.count(v) || new_dp[v] > cnt + 1)
new_dp[v] = cnt + 1;
}
}
dp = new_dp;
if (dp.empty()) return -1;
}
int res = INT_MAX;
for (auto [k, v] : dp) res = min(res, v);
return res;
}
};
import java.util.*;
class Solution {
public int makeArrayIncreasing(int[] arr1, int[] arr2) {
TreeSet<Integer> set = new TreeSet<>();
for (int num : arr2) set.add(num);
Map<Integer, Integer> dp = new HashMap<>();
dp.put(-1, 0);
for (int num : arr1) {
Map<Integer, Integer> newDp = new HashMap<>();
for (int prev : dp.keySet()) {
// Option 1: Keep arr1[i]
if (num > prev) {
newDp.put(num, Math.min(newDp.getOrDefault(num, Integer.MAX_VALUE), dp.get(prev)));
}
// Option 2: Replace with arr2 element
Integer next = set.higher(prev);
if (next != null) {
newDp.put(next, Math.min(newDp.getOrDefault(next, Integer.MAX_VALUE), dp.get(prev) + 1));
}
}
dp = newDp;
if (dp.isEmpty()) return -1;
}
int res = Integer.MAX_VALUE;
for (int val : dp.values()) res = Math.min(res, val);
return res;
}
}
var makeArrayIncreasing = function(arr1, arr2) {
arr2 = Array.from(new Set(arr2)).sort((a, b) => a - b);
let dp = new Map();
dp.set(-1, 0);
for (let num of arr1) {
let newDp = new Map();
for (let [prev, cnt] of dp.entries()) {
// Option 1: Keep arr1[i]
if (num > prev) {
if (!newDp.has(num) || newDp.get(num) > cnt)
newDp.set(num, cnt);
}
// Option 2: Replace with arr2 element
let l = 0, r = arr2.length;
while (l < r) {
let m = (l + r) >> 1;
if (arr2[m] > prev) r = m;
else l = m + 1;
}
if (l < arr2.length) {
let v = arr2[l];
if (!newDp.has(v) || newDp.get(v) > cnt + 1)
newDp.set(v, cnt + 1);
}
}
dp = newDp;
if (dp.size === 0) return -1;
}
return Math.min(...dp.values());
};
Given two integer arrays arr1 and arr2, your task is to make arr1 strictly increasing by performing the minimum number of operations. In one operation, you can replace any element in arr1 with any element from arr2.
arr2 more than once for replacements.arr1 must be strictly increasing (i.e., arr1[i] < arr1[i+1] for all valid i).arr1 strictly increasing, return -1.
The most direct approach is to try all possible ways to replace elements in arr1 with elements from arr2 and check if the result is strictly increasing. However, this brute-force method quickly becomes infeasible as the arrays grow, due to the huge number of combinations.
To optimize, we realize that at each index, we have two choices:
arr1[i] if it keeps the sequence strictly increasing.arr1[i] with an unused element from arr2 that is larger than the previous element.arr1. This suggests a dynamic programming or memoization approach, where the state is defined by the current position and the previous value used.
By using data structures that allow us to quickly find the next suitable element in arr2 (like sorting and binary search), we can cut down redundant checks and speed up the process.
Let's break down the steps to solve this problem efficiently:
arr2:
arr2 and remove duplicates to make binary search possible and avoid reusing the same value.dp where the key is the "previous value" used in the sequence, and the value is the minimum number of operations to reach this state.dp with -1: 0, meaning before the first element, the previous value is effectively -1 and no operation has been performed.arr1:
arr1, we create a new new_dp map to store possible states for the current position.dp, we consider two options:
new_dp accordingly.arr2 that is greater than the previous value (using binary search). If found, we can replace the current number with it (counting as one operation).dp = new_dp.dp is empty, it means no valid sequence is possible from here, so we return -1.dp.values() gives the minimum operations needed.This approach is efficient because:
arr2 is fast due to sorting.Let's work through an example:
Input: arr1 = [1,5,3,6,7], arr2 = [1,3,2,4]
arr2: [1,2,3,4]
dp = {-1: 0}
new_dp[1] = 0new_dp[1] = min(0, 1) (already 0)new_dp[5] = 0new_dp[2] = 1new_dp[3] = 1new_dp[3] = min(1,2) (already 1), try 4: new_dp[4] = 2new_dp[6] = 1new_dp[4] = 2new_dp[6] = min(1,2) (already 1)new_dp[7] = 1new_dp[7] = min(1,2) (already 1)
The minimum number of operations is 1 (replace 5 with 2).
arr1.arr2), and for each we do O(log m) binary search.
The key insight is to use dynamic programming to track, at each step, the minimum number of operations needed for each possible "previous value" in the strictly increasing sequence. By sorting arr2 and using binary search, we efficiently find replacement candidates. This approach transforms an exponential brute-force problem into a manageable one, leveraging both state pruning and fast lookups, resulting in an elegant and efficient solution.