给你两个整数数组 arr1
和 arr2
,返回使 arr1
严格递增所需要的最小「操作」数(可能为 0)。
每一步「操作」中,你可以分别从 arr1
和 arr2
中各选出一个索引,分别为 i
和 j
,0 <= i < arr1.length
和 0 <= j < arr2.length
,然后进行赋值运算 arr1[i] = arr2[j]
。
如果无法让 arr1
严格递增,请返回 -1
。
示例 1:
**输入:** arr1 = [1,5,3,6,7], arr2 = [1,3,2,4]
**输出:** 1
**解释:** 用 2 来替换 5,之后 arr1 = [1, 2, 3, 6, 7]。
示例 2:
**输入:** arr1 = [1,5,3,6,7], arr2 = [4,3,1]
**输出:** 2
**解释:** 用 3 来替换 5,然后用 4 来替换 3,得到 arr1 = [1, 3, 4, 6, 7]。
示例 3:
**输入:** arr1 = [1,5,3,6,7], arr2 = [1,6,3,3]
**输出:** -1
**解释:** 无法使 arr1 严格递增。
提示:
1 <= arr1.length, arr2.length <= 2000
0 <= arr1[i], arr2[i] <= 10^9
方法一:动态规划 思路与算法
此题为经典的「300. 最长递增子序列 」问题的变形题目,我们可以参考类似的题目解法。首先我们思考一下,由于要求数组严格递增,因此数组中不可能存在相同的元素,对于数组 arr}_2 来说,可以不需要考虑数组中的重复元素,可以预处理去除 arr}_2 的重复元素,假设数组 arr}_1 的长度为 n,数组 arr}_2 的长度为 m,此时可以知道最多可以替换的次数为 \min(n,m)。如何才能定义动态规划的递推公式,这就需要进行思考。我们设 dp}[i][j] 表示数组 arr}_1 中的前 i 个元素进行了 j 次替换后组成严格递增子数组末尾元素的最小值。当我们遍历 arr}_1 的第 i 个元素时,此时 arr}_1[i] 要么进行替换,要么进行保留,实际可以分类进行讨论:
此时如果 arr}_1[i] 需要进行保留,则 arr}_1[i] 一定严格大于前 i-1 个元素替换后组成的严格递增子数组最末尾的元素。假设前 i-1 个元素经过了 j 次变换后得到的递增子数组的末尾元素的最小值为 dp}[i-1][j],如果满足 arr}_1[i] > \textit{dp}[i-1][j],则此时 arr}_1[i] 可以保留加入到该子数组中且构成的数组严格递增;
此时如果 arr}_1[i] 需要进行替换,则替换后的元素一定严格大于前 i-1 个元素替换后组成的严格递增子数组最末尾的元素。假设前 i-1 个元素经过了 j-1 次变换后得到的递增子数组的末尾元素的最小值为 dp}[i-1][j-1],此时我们从 arr}_2 找到严格大于 dp}[i-1][j-1] 的最小元素 arr}_2[k],则此时将 arr}_2[k] 加入到该子数组中且构成数组严格递增;
综上可知,每个元素在替换时只有两种选择,要么选择保留当前元素 arr_1,要么从 arr_2 中选择一个满足条件的最小元素加入到数组中,最少替换方案一定包含在上述替换方法中。我们可以得到以下递推关系:
\begin{cases} \textit{dp}[i][j] = \min(\textit{dp}[i][j],\textit{arr}_1[i]), \quad & \textbf{if} \ \textit{arr}_1[i] > \textit{dp}[i-1][j] \ \textit{dp}[i][j] = \min(\textit{dp}[i][j],\textit{arr}_2[k]), \quad & \textbf{if} \ \textit{arr}_2[k] > \textit{dp}[i-1][j-1] \end{cases}
为了便于计算,我们将 dp}[i][j] 的初始值都设为 \infty,为了便于计算在最开始加一个哨兵,此时令 dp}[0][0] = -1 表示最小值。实际计算过程如下:
为了方便计算,需要对 arr}_2 进行预处理,去掉其中的重复元素,为了快速找到数组 arr}_2 中的最小元素,还需要对 arr}_2 进行排序;
依次尝试计算前 i 个元素在满足 j 次替换时的最小元素:
如果当前元素 arr}_1[i] 大于 dp}[i][j-1],此时可以尝试将 arr}_1[i] 替换为 dp}[i][j],即此时 dp}[i][j] = \min(\textit{dp}[i][j],\textit{arr}_1[i])。
如果前 i-1 个元素可以满足 j-1 次替换后成为严格递增数组,即满足 dp}[i-1][j-1] \neq \infty,可以尝试在第 j 次替换掉 arr}_1[i],此时根据贪心原则,利用二分查找可以快速的找到严格大于 dp}[i-1][j-1] 的最小值进行替换即可。
设当前数组 arr}_1[i] 的长度为 n,如果前 n 个元素满足 j 次替换后成为严格递增数组,此时我们找到最小的 j 返回即可。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 constexpr int INF = 0x3f3f3f3f ;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 ()); int n = arr1.size (); int m = arr2.size (); vector<vector<int >> dp (n + 1 , vector <int >(min (m, n) + 1 , INF)); dp[0 ][0 ] = -1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= min (i, m); j++) { if (arr1[i - 1 ] > dp[i - 1 ][j]) { dp[i][j] = arr1[i - 1 ]; } if (j > 0 && dp[i - 1 ][j - 1 ] != INF) { auto it = upper_bound (arr2.begin () + j - 1 , arr2.end (), dp[i - 1 ][j - 1 ]); if (it != arr2.end ()) { dp[i][j] = min (dp[i][j], *it); } } if (i == n && dp[i][j] != INF) { return j; } } } return -1 ; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { static final int INF = 0x3f3f3f3f ; public int makeArrayIncreasing (int [] arr1, int [] arr2) { Arrays.sort(arr2); List<Integer> list = new ArrayList <Integer>(); int prev = -1 ; for (int num : arr2) { if (num != prev) { list.add(num); prev = num; } } int n = arr1.length; int m = list.size(); int [][] dp = new int [n + 1 ][Math.min(m, n) + 1 ]; for (int i = 0 ; i <= n; i++) { Arrays.fill(dp[i], INF); } dp[0 ][0 ] = -1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= Math.min(i, m); j++) { if (arr1[i - 1 ] > dp[i - 1 ][j]) { dp[i][j] = arr1[i - 1 ]; } if (j > 0 && dp[i - 1 ][j - 1 ] != INF) { int idx = binarySearch(list, j - 1 , dp[i - 1 ][j - 1 ]); if (idx != list.size()) { dp[i][j] = Math.min(dp[i][j], list.get(idx)); } } if (i == n && dp[i][j] != INF) { return j; } } } return -1 ; } public int binarySearch (List<Integer> list, int low, int target) { int high = list.size(); while (low < high) { int mid = low + (high - low) / 2 ; if (list.get(mid) > target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Solution { const int INF = 0x3f3f3f3f ; public int MakeArrayIncreasing (int [] arr1, int [] arr2 ) { Array.Sort(arr2); IList<int > list = new List<int >(); int prev = -1 ; foreach (int num in arr2) { if (num != prev) { list.Add(num); prev = num; } } int n = arr1.Length; int m = list.Count; int [][] dp = new int [n + 1 ][]; for (int i = 0 ; i <= n; i++) { dp[i] = new int [Math.Min(m, n) + 1 ]; Array.Fill(dp[i], INF); } dp[0 ][0 ] = -1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= Math.Min(i, m); j++) { if (arr1[i - 1 ] > dp[i - 1 ][j]) { dp[i][j] = arr1[i - 1 ]; } if (j > 0 && dp[i - 1 ][j - 1 ] != INF) { int idx = BinarySearch(list, j - 1 , dp[i - 1 ][j - 1 ]); if (idx != list.Count) { dp[i][j] = Math.Min(dp[i][j], list[idx]); } } if (i == n && dp[i][j] != INF) { return j; } } } return -1 ; } public int BinarySearch (IList<int > list, int low, int target ) { int high = list.Count; while (low < high) { int mid = low + (high - low) / 2 ; if (list[mid] > target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution : def makeArrayIncreasing (self, arr1: List [int ], arr2: List [int ] ) -> int : arr2 = sorted (set (arr2)) n = len (arr1) m = len (arr2) dp = [[inf] *(min (m, n)+1 ) for _ in range (n + 1 )] dp[0 ][0 ] = -1 for i in range (1 , n + 1 ): for j in range (min (i, m) + 1 ): if arr1[i - 1 ] > dp[i - 1 ][j]: dp[i][j] = arr1[i - 1 ] if j and dp[i - 1 ][j - 1 ] != inf: k = bisect_right(arr2, dp[i - 1 ][j - 1 ], j - 1 ) if k < m: dp[i][j] = min (dp[i][j], arr2[k]) if i == n and dp[i][j] != inf: return j return -1
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #define MIN(a, b) ((a) < (b) ? (a) : (b)) const int INF = 0x3f3f3f3f ;static int cmp (const void *pa, const void *pb) { return *(int *)pa - *(int *)pb; } int binarySearch (int *arr, int left, int right, int val) { int ret = right + 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (arr[mid] > val) { ret = mid; right = mid - 1 ; } else { left = mid + 1 ; } } return ret; } int min (int a, int b) { return a < b ? a : b; } int makeArrayIncreasing (int * arr1, int arr1Size, int * arr2, int arr2Size) { qsort(arr2, arr2Size, sizeof (int ), cmp); int m = 0 ; for (int i = 0 ; i < arr2Size; i++) { if (i == 0 || arr2[i] != arr2[i - 1 ]) { arr2[m++] = arr2[i]; } } int n = arr1Size; int dp[n + 1 ][min(n, m) + 1 ]; memset (dp, 0x3f , sizeof (dp)); dp[0 ][0 ] = -1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= min(i, m); j++) { if (arr1[i - 1 ] > dp[i - 1 ][j]) { dp[i][j] = arr1[i - 1 ]; } if (j > 0 && dp[i - 1 ][j - 1 ] != INF) { int index = binarySearch(arr2, j - 1 , m - 1 , dp[i - 1 ][j - 1 ]); if (index != m) { dp[i][j] = MIN(dp[i][j], arr2[index]); } } if (i == n && dp[i][j] != INF) { return j; } } } return -1 ; }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 const INF = 0x3f3f3f3f ;var makeArrayIncreasing = function (arr1, arr2 ) { arr2.sort ((a, b ) => a - b); const list = []; let prev = -1 ; for (const num of arr2) { if (num !== prev) { list.push (num); prev = num; } } const n = arr1.length ; const m = list.length ; const dp = new Array (n + 1 ).fill (0 ).map (() => new Array (Math .min (m, n) + 1 ).fill (INF )); dp[0 ][0 ] = -1 ; for (let i = 1 ; i <= n; i++) { for (let j = 0 ; j <= Math .min (i, m); j++) { if (arr1[i - 1 ] > dp[i - 1 ][j]) { dp[i][j] = arr1[i - 1 ]; } if (j > 0 && dp[i - 1 ][j - 1 ] !== INF ) { const idx = binarySearch (list, j - 1 , dp[i - 1 ][j - 1 ]); if (idx !== list.length ) { dp[i][j] = Math .min (dp[i][j], list[idx]); } } if (i === n && dp[i][j] !== INF ) { return j; } } } return -1 ; } const binarySearch = (list, low, target ) => { let high = list.length ; while (low < high) { const mid = low + Math .floor ((high - low) / 2 ); if (list[mid] > target) { high = mid; } else { low = mid + 1 ; } } return low; };
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 func makeArrayIncreasing (arr1 []int , arr2 []int ) int { sort.Ints(arr2) n := len (arr1) m := len (arr2) dp := make ([][]int , n+1 ) for i := range dp { dp[i] = make ([]int , min(m, n)+1 ) for j := range dp[i] { dp[i][j] = math.MaxInt } } dp[0 ][0 ] = -1 for i := 1 ; i <= len (arr1); i++ { for j := 0 ; j <= min(i, m); j++ { if arr1[i-1 ] > dp[i-1 ][j] { dp[i][j] = arr1[i-1 ] } if j > 0 && dp[i-1 ][j-1 ] != math.MaxInt { k := j - 1 + sort.SearchInts(arr2[j-1 :], dp[i-1 ][j-1 ]+1 ) if k < m { dp[i][j] = min(dp[i][j], arr2[k]) } } if i == n && dp[i][j] != math.MaxInt { return j } } } return -1 } func min (a, b int ) int { if a > b { return b } return a }
复杂度分析
时间复杂度:O(n \times \min(m,n) \times \log m),其中 n 表示数组 arr}_1 的长度,m 表示数组 arr}_2 的长度。每次替换时,我们都需利用二分查找找到最小的元素,此时需要的时间为 O(\log m),最多需要尝试 n \times \min(m,n) 种替换方案,因此总的时间复杂度为 O(n \times \min(m,n) \times \log m)。
空间复杂度:O(n \times \min(m,n)),其中 n 表示数组 arr}_1 的长度,m 表示数组 arr}_2 的长度。我们需要保存每个子数组中替换次数下的末尾元素的最大值,一共最多有 n 个子数组,每个子数组替换替换的次数最多为 \min(n,m) 次数,因此空间复杂度为 O(n \times \min(m,n))。
方法二:动态规划二 思路与算法
根据方法一的提示,我们可以变换一种动态规划的思路。我们可以观察到实际在替换时,假设当前数组 arr}_1 前 i 个元素最少经过 j 次替换成为严格递增数组,实际情况替换如下:
前 i-1 个元素进行了 j 次替换,此时 {arr}_1[i] 保留;
前 i-1 个元素进行了 j-1 次替换,此时 {arr}_1[i] 被替换;
根据以上想法,定义 dp}[i] 为使数组 {arr}_1[i] 的前 i 项递增,且保留 {arr}_1[i] 的情况下的最小替换次数。
为什么只考虑不替换 arr}_1[i] 的状态,因为如果替该元素,那么到底替换成哪个元素,此时需要另加一个状态维护。根据上述分析由于数组 {arr}_1 中的每个元素都可能被替换,{arr}_1 的最后一项也可能被替换,此时我们可以在数组最后增加一个非常大的数,而保证这个数不替换即可,这样即可保证当前的子状态中一定包含最优解。
首先为了方便计算我们对数组进行预处理,对于数组 arr}_2 来说,可以不需要考虑数组中的重复元素,可以预处理去除 arr}_2 的重复元素,假设数组 arr}_1 的长度为 n,数组 arr}_2 的长度为 m,此时可以知道最多可以替换的次数为 \min(n,m)。
状态转移
对于第 i 个元素考虑 dp}[i],由于我们不能替换 arr}_1[i],假设 i 之前上一个被保留的元素为 arr}_1[k],则此时 [k+1,i-1] 之间的元素均被替换,即此时 arr}_1[k+1],\textit{arr}_1[k+2],\cdots,\textit{arr}_1[i] 连续的 i-k-1 个元素均被替换,此时需要的最小的替换次数可能为 dp}[k] + i - k + 1。由于可能存在 i 之前所有的元素均被替换的情形,此时我们可以在数组前面增加一个非常小的数,而保证这个数一定不被替换。
根据以上分析可知,我们需要尝试替换 i 之前的连续 j 个元素,分为以下两种情形讨论:
arr}_1[i] 之前替换的元素为 0 个,即此时保留 arr}_1[i-1]。如果要保留 arr}_1[i-1],则此时一定满足 arr}_1[i-1] < \textit{arr}_1[i],此时递推公式为:
\textit{dp}[i] = \min(\textit{dp}[i],\textit{dp}[i-1])
arr}_1[i] 之前替换的元素为 j 个,此时 j > 0,此时 arr}_1[i],\textit{arr}_1[i-j-1] 均被保留,此时 arr}_1[i-j],\textit{arr}_1[i-j+1],\cdots,\textit{arr}_1[i-1] 连续的 j 个元素被替换。如何上述替换才能一定能成立呢?此时最优选择肯定是我们在 arr}_2 中也找到连续的 j 个元素来替换他们即可。假设替换的 j 个元素为 arr}_2[k],\textit{arr}_2[k+1],\cdots,\textit{arr}_2[k+j-1],由于 arr}_2 已经是有序的,此时一定满足 arr}_2[k] < \textit{arr}_2[k+1] < \cdots < \textit{arr}_2[k+j-1],则这 j 个元素需要满足如下条件即可进行替换:
最小的元素 arr}_2[k] 一定需要大于 arr}_1[i-j-1];
最大的元素 arr}_2[k+j-1] 一定需要小于 arr}_1[i];
上述情形下的此时递推公式即为:
\begin{aligned} \textit{dp}[i] &= \min(\textit{dp}[i],\textit{dp}[i-j+1] + j) \ & \mathbf{if\ exist}\ {arr}_2[k] > \textit{arr}_1[i-j-1], \textit{arr}_2[k+j-1] < \textit{arr}_1[i] \end{aligned}
根据以上分析可以知道题目难点在于如何找到连续替换的元素,给定 arr}_1 的第 i 个元素,此时需要替换 arr}_1 前面的 j 个元素,是否可以在 arr}_2 中找到连续的 j 个元素,其中最小的元素满足大于 arr}_1[i-j-1],最大的元素满足小于 arr}_1[i],此时根据贪心原则,我们可以用以下两种办法均可:
查找替换元素的左侧起点:通过二分查找可以在 O(\log m) 时间复杂度内找到严格大于 arr}_1[i-j-1] 的最小元素 arr}_2[k]。由于我们需要替换 j 个元素,再检测替换的最大元素 arr}_2[k + i - j] 是否小于 arr}_1[i] 即可;
查找替换元素的右侧终点:通过二分查找可以在 O(\log m) 时间复杂度内找到严格小于 arr}_1[i] 的最大元素 arr}_2[k]。由于我们需要替换 j 个元素,再检测替换的最小元素 arr}_2[k - j + 1] 是否大于 arr}_1[i-j-1] 即可;
由于数组 arr}_1[i] 起点与终点的「哨兵」一定不会被替换的,因此添加「哨兵」不影响最终结果,最终返回 dp}[n] 即可。
代码
[sol2.1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 constexpr int INF = 0x3f3f3f3f ;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 ()); arr1.push_back (INF); arr1.insert (arr1.begin (), -1 ); int n = arr1.size (); int m = arr2.size (); vector<int > dp (n, INF) ; dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = min (dp[i], dp[i - 1 ]); } for (int j = 1 ; j < i; j++) { int k = upper_bound (arr2.begin (), arr2.end (), arr1[i - j - 1 ]) - arr2.begin (); if (k + j - 1 < m && arr2[k + j - 1 ] < arr1[i]) { dp[i] = min (dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } };
[sol2.1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution { static final int INF = 0x3f3f3f3f ; public int makeArrayIncreasing (int [] arr1, int [] arr2) { Arrays.sort(arr2); List<Integer> list = new ArrayList <Integer>(); int prev = -1 ; for (int num : arr2) { if (num != prev) { list.add(num); prev = num; } } int [] temp = new int [arr1.length + 2 ]; System.arraycopy(arr1, 0 , temp, 1 , arr1.length); temp[arr1.length + 1 ] = INF; temp[0 ] = -1 ; arr1 = temp; int n = arr1.length; int m = list.size(); int [] dp = new int [n]; Arrays.fill(dp, INF); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math.min(dp[i], dp[i - 1 ]); } for (int j = 1 ; j < i; j++) { int k = binarySearch(list, arr1[i - j - 1 ]); if (k + j - 1 < m && list.get(k + j - 1 ) < arr1[i]) { dp[i] = Math.min(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } public int binarySearch (List<Integer> list, int target) { int low = 0 , high = list.size(); while (low < high) { int mid = low + (high - low) / 2 ; if (list.get(mid) > target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol2.1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class Solution { const int INF = 0x3f3f3f3f ; public int MakeArrayIncreasing (int [] arr1, int [] arr2 ) { Array.Sort(arr2); IList<int > list = new List<int >(); int prev = -1 ; foreach (int num in arr2) { if (num != prev) { list.Add(num); prev = num; } } int [] temp = new int [arr1.Length + 2 ]; Array.Copy(arr1, 0 , temp, 1 , arr1.Length); temp[arr1.Length + 1 ] = INF; temp[0 ] = -1 ; arr1 = temp; int n = arr1.Length; int m = list.Count; int [] dp = new int [n]; Array.Fill(dp, INF); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math.Min(dp[i], dp[i - 1 ]); } for (int j = 1 ; j < i; j++) { int k = BinarySearch(list, arr1[i - j - 1 ]); if (k + j - 1 < m && list[k + j - 1 ] < arr1[i]) { dp[i] = Math.Min(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } public int BinarySearch (IList<int > list, int target ) { int low = 0 , high = list.Count; while (low < high) { int mid = low + (high - low) / 2 ; if (list[mid] > target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol2.1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #define MIN(a, b) ((a) < (b) ? (a) : (b)) const int INF = 0x3f3f3f3f ;static int cmp (const void *pa, const void *pb) { return *(int *)pa - *(int *)pb; } int binarySearch (int *arr, int left, int right, int val) { int ret = right + 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (arr[mid] > val) { ret = mid; right = mid - 1 ; } else { left = mid + 1 ; } } return ret; } int makeArrayIncreasing (int * arr1, int arr1Size, int * arr2, int arr2Size) { int n = arr1Size + 2 ; int arr[n]; memcpy (arr + 1 , arr1, sizeof (int ) * arr1Size); arr1 = arr; arr1[0 ] = -1 ; arr1[arr1Size + 1 ] = INF; qsort(arr2, arr2Size, sizeof (int ), cmp); int m = 0 ; for (int i = 0 , j = 0 ; i < arr2Size; i++) { if (i == 0 || arr2[i] != arr2[i - 1 ]) { arr2[m++] = arr2[i]; } } int dp[n]; memset (dp, 0x3f , sizeof (dp)); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = MIN(dp[i], dp[i - 1 ]); } for (int j = 1 ; j < i; j++) { int k = binarySearch(arr2, 0 , m - 1 , arr1[i - j - 1 ]); if (k + j - 1 < m && arr2[k + j - 1 ] < arr1[i]) { dp[i] = MIN(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; }
[sol2.1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 const INF = 0x3f3f3f3f ;var makeArrayIncreasing = function (arr1, arr2 ) { arr2.sort ((a, b ) => a - b); const list = []; let prev = -1 ; for (const num of arr2) { if (num !== prev) { list.push (num); prev = num; } } const temp = new Array (arr1.length + 2 ).fill (0 ); temp.splice (1 , arr1.length , ...arr1); temp[arr1.length + 1 ] = INF ; temp[0 ] = -1 ; arr1 = temp; const n = arr1.length ; const m = list.length ; const dp = new Array (n).fill (INF ); dp[0 ] = 0 ; for (let i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math .min (dp[i], dp[i - 1 ]); } for (let j = 1 ; j < i; j++) { const k = binarySearch (list, arr1[i - j - 1 ]); if (k + j - 1 < m && list[k + j - 1 ] < arr1[i]) { dp[i] = Math .min (dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] === INF ? -1 : dp[n - 1 ]; } const binarySearch = (list, target ) => { let low = 0 , high = list.length ; while (low < high) { const mid = low + Math .floor ((high - low) / 2 ); if (list[mid] > target) { high = mid; } else { low = mid + 1 ; } } return low; };
上述方法我们可以进行进一步优化,首先可以利用二分查找找到替换元素的右侧终点,利用二分查找找到严格小于 arr}_1[i] 的最大元素 arr}_2[k],然后从 k 起始依次向前枚举连续替换元素的个数 j,即 [\textit{arr}_1[i-j],\textit{arr}_1[i-j+1],\cdots,\textit{arr}_1[i-1]] 连续 j 个元素被替换,此时只需要检测 arr}_2[k-j] > \textit{arr}_1[i-j-1] 即可,时间复杂度可以进一步优化。
[sol2.2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 constexpr int INF = 0x3f3f3f3f ;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 ()); arr1.push_back (INF); arr1.insert (arr1.begin (), -1 ); int n = arr1.size (); int m = arr2.size (); vector<int > dp (n, INF) ; dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = min (dp[i], dp[i - 1 ]); } int k = lower_bound (arr2.begin (), arr2.end (), arr1[i]) - arr2.begin (); for (int j = 1 ; j <= min (i - 1 , k); ++j) { if (arr1[i - j - 1 ] < arr2[k - j]) { dp[i] = min (dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } };
[sol2.2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Solution { static final int INF = 0x3f3f3f3f ; public int makeArrayIncreasing (int [] arr1, int [] arr2) { Arrays.sort(arr2); List<Integer> list = new ArrayList <Integer>(); int prev = -1 ; for (int num : arr2) { if (num != prev) { list.add(num); prev = num; } } int [] temp = new int [arr1.length + 2 ]; System.arraycopy(arr1, 0 , temp, 1 , arr1.length); temp[arr1.length + 1 ] = INF; temp[0 ] = -1 ; arr1 = temp; int n = arr1.length; int m = list.size(); int [] dp = new int [n]; Arrays.fill(dp, INF); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math.min(dp[i], dp[i - 1 ]); } int k = binarySearch(list, arr1[i]); for (int j = 1 ; j <= Math.min(i - 1 , k); ++j) { if (arr1[i - j - 1 ] < list.get(k - j)) { dp[i] = Math.min(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } public int binarySearch (List<Integer> list, int target) { int low = 0 , high = list.size(); while (low < high) { int mid = low + (high - low) / 2 ; if (list.get(mid) >= target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol2.2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Solution { const int INF = 0x3f3f3f3f ; public int MakeArrayIncreasing (int [] arr1, int [] arr2 ) { Array.Sort(arr2); IList<int > list = new List<int >(); int prev = -1 ; foreach (int num in arr2) { if (num != prev) { list.Add(num); prev = num; } } int [] temp = new int [arr1.Length + 2 ]; Array.Copy(arr1, 0 , temp, 1 , arr1.Length); temp[arr1.Length + 1 ] = INF; temp[0 ] = -1 ; arr1 = temp; int n = arr1.Length; int m = list.Count; int [] dp = new int [n]; Array.Fill(dp, INF); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math.Min(dp[i], dp[i - 1 ]); } int k = BinarySearch(list, arr1[i]); for (int j = 1 ; j <= Math.Min(i - 1 , k); ++j) { if (arr1[i - j - 1 ] < list[k - j]) { dp[i] = Math.Min(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; } public int BinarySearch (IList<int > list, int target ) { int low = 0 , high = list.Count; while (low < high) { int mid = low + (high - low) / 2 ; if (list[mid] >= target) { high = mid; } else { low = mid + 1 ; } } return low; } }
[sol2.2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #define MIN(a, b) ((a) < (b) ? (a) : (b)) const int INF = 0x3f3f3f3f ;static int cmp (const void *pa, const void *pb) { return *(int *)pa - *(int *)pb; } int binarySearch (int *arr, int left, int right, int val) { int ret = right + 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (arr[mid] >= val) { ret = mid; right = mid - 1 ; } else { left = mid + 1 ; } } return ret; } int makeArrayIncreasing (int * arr1, int arr1Size, int * arr2, int arr2Size) { int n = arr1Size + 2 ; int arr[n]; memcpy (arr + 1 , arr1, sizeof (int ) * arr1Size); arr1 = arr; arr1[0 ] = -1 ; arr1[arr1Size + 1 ] = INF; qsort(arr2, arr2Size, sizeof (int ), cmp); int m = 0 ; for (int i = 0 , j = 0 ; i < arr2Size; i++) { if (i == 0 || arr2[i] != arr2[i - 1 ]) { arr2[m++] = arr2[i]; } } int dp[n]; memset (dp, 0x3f , sizeof (dp)); dp[0 ] = 0 ; for (int i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = MIN(dp[i], dp[i-1 ]); } int k = binarySearch(arr2, 0 , m - 1 , arr1[i]); for (int j = 1 ; j <= MIN(i - 1 , k); ++j) { if (arr1[i - j - 1 ] < arr2[k - j]) { dp[i] = MIN(dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] == INF ? -1 : dp[n - 1 ]; }
[sol2.2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 const INF = 0x3f3f3f3f ;var makeArrayIncreasing = function (arr1, arr2 ) { arr2.sort ((a, b ) => a - b); const list = []; let prev = -1 ; for (const num of arr2) { if (num !== prev) { list.push (num); prev = num; } } const temp = new Array (arr1.length + 2 ).fill (0 ); temp.splice (1 , arr1.length , ...arr1); temp[arr1.length + 1 ] = INF ; temp[0 ] = -1 ; arr1 = temp; const n = arr1.length ; const m = list.length ; const dp = new Array (n).fill (INF ); dp[0 ] = 0 ; for (let i = 1 ; i < n; i++) { if (arr1[i - 1 ] < arr1[i]) { dp[i] = Math .min (dp[i], dp[i - 1 ]); } const k = binarySearch (list, arr1[i]); for (let j = 1 ; j <= Math .min (i - 1 , k); ++j) { if (arr1[i - j - 1 ] < list[k - j]) { dp[i] = Math .min (dp[i], dp[i - j - 1 ] + j); } } } return dp[n - 1 ] === INF ? -1 : dp[n - 1 ]; } const binarySearch = (list, target ) => { let low = 0 , high = list.length ; while (low < high) { const mid = low + Math .floor ((high - low) / 2 ); if (list[mid] >= target) { high = mid; } else { low = mid + 1 ; } } return low; };
复杂度分析
时间复杂度:O(n \times (\log m + \min(m,n))),其中 n 表示数组 arr}_1 的长度,m 表示数组 arr}_2 的长度。每次对 i 之前的元素替换时,我们都需利用二分查找找到 arr}_2 中子数组右侧的终点,需要的时间复杂度为 O(\log m),然后依次枚举子数组的起点,一共最多需要枚举 \min(m,n) 次,因此总的时间复杂度为 O(n \times (\log m + \min(m,n)))。
空间复杂度:O(n),其中 n 表示数组的长度。根据题目定义最多只有 n 个状态,因此需要的空间为 O(n)。