[HOT 100] 1187. 使数组严格递增

1. 题目链接


1187. 使数组严格递增 - 力扣(LeetCode)


2. 题目描述


给你两个整数数组 arr1arr2,返回使 arr1 严格递增所需要的最小「操作」数(可能为 0)。

每一步「操作」中,你可以分别从 arr1arr2 中各选出一个索引,分别为 ij0 <= i < arr1.length0 <= j < arr2.length,然后进行赋值运算 arr1[i] = arr2[j]

如果无法让 arr1 严格递增,请返回 -1

3. 题目示例


示例 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 严格递增。

4. 解题思路


  1. 问题理解:该算法解决的是如何通过最少的替换操作(将a中的元素替换为b中的元素)使得数组a严格递增。
  2. 核心思想:使用动态规划+记忆化搜索的方法:
    • 对数组b进行排序以便二分查找
    • 从数组a的末尾开始向前处理
    • 对于每个元素,考虑两种情况:替换或不替换
    • 使用记忆化存储中间结果避免重复计算
  3. 关键步骤
    • 排序数组b以便快速查找合适的替换值
    • 递归处理每个元素,比较替换和不替换的情况
    • 使用二分查找快速定位b中小于当前前驱值的最大数
    • 记忆化存储计算结果优化性能
  4. 边界处理
    • 初始时假设最后一个元素后面有一个无穷大的数
    • 使用Integer.MAX_VALUE/2作为无法完成操作的标志

5. 题解代码


class Solution {
    private int[] a, b; // 原始数组a和备用数组b
    private Map<Integer, Integer> memo[]; // 记忆化存储,用于避免重复计算

    public int makeArrayIncreasing(int[] a, int[] b) {
        this.a = a;
        this.b = b;
        Arrays.sort(b); // 为能二分查找,对b排序
        int n = a.length;
        memo = new HashMap[n]; // 初始化记忆化数组
        Arrays.setAll(memo, e -> new HashMap<>());
        // 从最后一个元素开始处理,假设a[n-1]右侧有个无穷大的数
        int ans = dfs(n - 1, Integer.MAX_VALUE); 
        // 如果结果过大说明无法完成,返回-1
        return ans < Integer.MAX_VALUE / 2 ? ans : -1;
    }

    private int dfs(int i, int pre) {
        if (i < 0) return 0; // 基本情况:处理完所有元素
        if (memo[i].containsKey(pre))
            return memo[i].get(pre); // 如果之前计算过,直接返回结果

        // 不替换a[i]的情况
        int res = a[i] < pre ? dfs(i - 1, a[i]) : Integer.MAX_VALUE / 2;
        
        // 二分查找b中小于pre的最大数的下标
        int k = lowerBound(b, pre) - 1;
        if (k >= 0) // 如果找到合适的替换数
            res = Math.min(res, dfs(i - 1, b[k]) + 1); // 比较替换和不替换的最小操作数

        memo[i].put(pre, res); // 存储计算结果
        return res;
    }

    // 二分查找辅助函数,找到第一个大于等于target的数的位置
    private int lowerBound(int[] nums, int target) {
        int left = -1, right = nums.length; // 开区间(left, right)
        while (left + 1 < right) { // 区间不为空
            int mid = (left + right) >>> 1; // 无符号右移防止溢出
            if (nums[mid] < target)
                left = mid; // 范围缩小到(mid, right)
            else
                right = mid; // 范围缩小到(left, mid)
        }
        return right;
    }
}


6. 复杂度分析


时间复杂度:O(n_m_logm),其中n是数组a的长度,m是数组b的长度。这是因为:

  • 排序b数组需要O(m logm)时间
  • 记忆化搜索中,每个状态(i, pre)最多被计算一次,其中i有n种可能,pre最多有m种可能(因为pre只能是b中的某个值或a中的原始值)
  • 对于每个状态,我们需要执行一次二分查找,耗时O(logm)

空间复杂度:O(n*m),主要用于存储记忆化结果。记忆化数组memo的大小为n,每个元素是一个哈希表,在最坏情况下可能存储m个键值对。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值