Make Array Strictly Increasing

hard dynamic programming binary search array

Problem

Given two integer arrays arr1 and arr2, in one operation you may pick an index i of arr1 and replace arr1[i] with any element of arr2. Return the minimum number of operations needed to make arr1 strictly increasing, or -1 if it is impossible.

Inputarr1 = [1, 5, 3, 6, 7], arr2 = [1, 3, 2, 4]
Output1
Replace arr1[2] = 3 with 4, giving [1, 5, 4, 6, 7], which is strictly increasing — one operation.

import bisect

def make_array_increasing(arr1, arr2):
    arr2 = sorted(set(arr2))
    # dp: maps "last value of prefix" -> min operations
    dp = {-1: 0}
    for x in arr1:
        ndp = {}
        for last, cost in dp.items():
            # Option 1: keep arr1[i] if it stays increasing
            if x > last:
                ndp[x] = min(ndp.get(x, 10**9), cost)
            # Option 2: replace with smallest arr2 value > last
            j = bisect.bisect_right(arr2, last)
            if j < len(arr2):
                v = arr2[j]
                ndp[v] = min(ndp.get(v, 10**9), cost + 1)
        dp = ndp
        if not dp:
            return -1
    return min(dp.values())
function makeArrayIncreasing(arr1, arr2) {
  arr2 = [...new Set(arr2)].sort((a, b) => a - b);
  let dp = new Map([[-Infinity, 0]]);
  for (const x of arr1) {
    const ndp = new Map();
    for (const [last, cost] of dp) {
      if (x > last) {
        ndp.set(x, Math.min(ndp.get(x) ?? Infinity, cost));
      }
      let lo = 0, hi = arr2.length;
      while (lo < hi) {
        const mid = (lo + hi) >> 1;
        if (arr2[mid] > last) hi = mid; else lo = mid + 1;
      }
      if (lo < arr2.length) {
        const v = arr2[lo];
        ndp.set(v, Math.min(ndp.get(v) ?? Infinity, cost + 1));
      }
    }
    dp = ndp;
    if (dp.size === 0) return -1;
  }
  return Math.min(...dp.values());
}
class Solution {
    public int makeArrayIncreasing(int[] arr1, int[] arr2) {
        TreeSet<Integer> set = new TreeSet<>();
        for (int v : arr2) set.add(v);
        Integer[] a2 = set.toArray(new Integer[0]);
        Map<Integer, Integer> dp = new HashMap<>();
        dp.put(Integer.MIN_VALUE, 0);
        for (int x : arr1) {
            Map<Integer, Integer> ndp = new HashMap<>();
            for (Map.Entry<Integer, Integer> e : dp.entrySet()) {
                int last = e.getKey(), cost = e.getValue();
                if (x > last)
                    ndp.merge(x, cost, Math::min);
                Integer v = set.higher(last);
                if (v != null)
                    ndp.merge(v, cost + 1, Math::min);
            }
            dp = ndp;
            if (dp.isEmpty()) return -1;
        }
        int best = Integer.MAX_VALUE;
        for (int c : dp.values()) best = Math.min(best, c);
        return best;
    }
}
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{{INT_MIN, 0}};
    for (int x : arr1) {
        map<int, int> ndp;
        for (auto& [last, cost] : dp) {
            if (x > last) {
                if (!ndp.count(x) || ndp[x] > cost) ndp[x] = cost;
            }
            auto it = upper_bound(arr2.begin(), arr2.end(), last);
            if (it != arr2.end()) {
                int v = *it;
                if (!ndp.count(v) || ndp[v] > cost + 1) ndp[v] = cost + 1;
            }
        }
        dp = ndp;
        if (dp.empty()) return -1;
    }
    int best = INT_MAX;
    for (auto& [k, c] : dp) best = min(best, c);
    return best;
}
Time: O(n · m · log m) Space: O(m)