leetcode
leetcode 1101 ~ 1150
使数组严格递增

使数组严格递增

难度:

标签:

题目描述

代码结果

运行时间: 127 ms, 内存: 16.6 MB


/*
 * Problem: Given two integer arrays arr1 and arr2, return the minimum number of operations required to make arr1 strictly increasing by replacing elements from arr1 with elements from arr2. If it is impossible, return -1.
 * Approach: Use dynamic programming and Java Streams to process the arrays and keep track of the minimum operations required for each possible ending value of arr1 up to each index.
 */
import java.util.Arrays;
import java.util.stream.IntStream;

public class Solution {
    public int makeArrayIncreasing(int[] arr1, int[] arr2) {
        Arrays.sort(arr2);
        int n = arr1.length;
        int m = arr2.length;
        int[][] dp = new int[n + 1][m + 1];
        for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE);
        dp[0][0] = 0;

        IntStream.range(1, n + 1).forEach(i -> {
            IntStream.rangeClosed(0, m).forEach(j -> {
                if (j == 0 || arr2[j - 1] < arr1[i - 1]) {
                    dp[i][0] = Math.min(dp[i][0], dp[i - 1][j]);
                }
                if (j > 0 && arr2[j - 1] < arr1[i - 1]) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][0] + 1);
                }
                if (j > 0 && arr2[j - 1] < arr2[j - 2]) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            });
        });

        return IntStream.rangeClosed(0, m).map(j -> dp[n][j]).min().orElse(Integer.MAX_VALUE) == Integer.MAX_VALUE ? -1 : IntStream.rangeClosed(0, m).map(j -> dp[n][j]).min().orElse(Integer.MAX_VALUE);
    }
}

解释

方法:

这个题解使用了动态规划和二分查找。具体思路是:对于 arr1 的每个位置 i,考虑是否替换 arr1[i] 以及替换成 arr2 中的哪个元素。通过递归函数 dfs(i) 来计算arr1[0:i+1] 变为严格递增序列所需的最小替换次数。 在 dfs(i) 中,首先用二分查找找到 arr2 中严格大于 arr1[i] 的最小元素的下标 k。然后比较两种可能性: 1. 不替换 arr1[i],只需 arr1[i-1] < arr1[i],这种情况下替换次数为 dfs(i-1)。 2. 替换 arr1[i],需要枚举替换的起始位置 j,满足 j < i-1 且 arr2[k-(i-j-1)] > arr1[j],即用arr2[k-(i-j-1):k] 替换 arr1[j+1:i],这种情况下替换次数为 dfs(j)+i-j-1。最后取所有可能性的最小值。 通过添加 @cache 装饰器来缓存dfs结果,避免重复计算。如果最终 dfs(n) < n(n为数组长度),则说明可以得到严格递增序列,返回替换次数 n-dfs(n),否则返回-1。

时间复杂度:

O(n * min(n, m) * log m)

空间复杂度:

O(n + m)

代码细节讲解

🦆
为什么在动态规划的状态转移过程中考虑两种情况:不替换当前元素和替换当前元素?这样的分类有什么优势?
在动态规划中考虑不替换和替换当前元素两种情况,是为了覆盖所有可能的操作以确保找到最优解。这种分类使问题分解为更小、更可管理的子问题。不替换时,问题简化为前一个元素的状态;替换时,则考虑通过替换达到递增顺序的可能性。这样做可以确保无论数组当前的状态如何,都能评估所有可能的操作,从而找到使整个数组严格递增的最小替换次数。
🦆
在递归函数dfs中,为什么需要使用二分查找来确定arr2中严格大于arr1[i]的最小元素?这对算法的效率提升有何帮助?
使用二分查找来确定arr2中严格大于arr1[i]的最小元素可以显著提高算法效率。二分查找是一种高效的搜索方法,其时间复杂度为O(log n),远快于线性搜索的O(n)。在动态规划的每个步骤中,都需要确定一个适合替换的最小元素,二分查找可以快速定位这个元素,从而减少每次递归调用的计算量,提高整体算法的执行速度。
🦆
题解中提到的枚举替换起始位置j的逻辑,为何要确保j小于i-1并且arr2[k-(i-j-1)] > arr1[j]?这样的条件设置是基于什么考虑?
这样的条件设置是为了确保替换操作能使数组部分段成为严格递增序列。条件j小于i-1是为了避免在同一位置重复替换,同时确保有足够的元素可以进行替换。条件arr2[k-(i-j-1)] > arr1[j]是为了确保替换后的序列在j处能维持严格递增,即替换的起点元素必须大于arr1中当前考虑的替换起始位置的元素,这是保持序列严格递增的关键条件。
🦆
在处理边界情况时,如何确保算法在arr1或arr2为空时仍然能正确运行?
在算法实现中,应该添加特定的边界条件检查来处理arr1或arr2为空的情况。如果arr1为空,理论上不需要任何替换,可以直接返回0。如果arr2为空,则无法进行任何替换操作,应检查arr1是否已经是严格递增的,如果是,则返回0;如果不是,则返回-1表示无法通过替换达到严格递增。这样的边界条件处理确保了算法在所有输入情况下都能给出正确的结果。

相关问题