使数组严格递增

给你两个整数数组 arr1 和 arr2,返回使 arr1 严格递增所需要的最小「操作」数(可能为 0)。
每一步「操作」中,你可以分别从 arr1 和 arr2 中各选出一个索引,
分别为 i 和 j,0 <= i < arr1.length 和 0 <= j < arr2.length,然后进行赋值运算 arr1[i] = arr2[j]。

一. 动态规划

分析:首先对arr2进行排序和去重,每次贪心选择arr2中满足条件的最小值进行替换
能替换的最多次数为arr2的的长度,这样我们只用关心arr1的递增与否即可
arr1数组的递增与子数组的递增是相关联的,满足无后效性和重叠性,考虑使用动态规划
由于最后要返回的是最小操作次数,我们可以先考虑一定操作次数是否能使得子数组递增
为了关联子数组与整个数组,同时满足最小操作次数

dp[i][j]表示数组 arr1中的前 i 个元素进行了 j 次替换后组成严格递增子数组末尾元素的最小值
这样既可以告诉后面的元素,前面子数组经过j次操作能否递增,也将其有用的信息传给了后面

class Solution {
public:
    int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
        sort(arr2.begin(), arr2.end());//排序
        auto newend = unique(arr2.begin(), arr2.end());
        arr2.erase(newend, arr2.end());//去重
        int n = arr1.size();
        int m = arr2.size();
        vector<vector<int>> dp(n + 1, vector<int>(min(m, n) + 1, INT_MAX));
        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] != INT_MAX) {//可以进行一次修改
                    //在arr2中找下一个候选数
                    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] != INT_MAX) //使得整个数组递增,返回最小操作数
                    return j;
            }
        }
        return -1;
    }
};

固定最后一个数的思路(类似最长递增序列)
dp[i][j]为对于前i个数构成的递增数组,在最后一个数为j时的最小操作数
这样也能方便实现状态的转移,使用滚动dp节省空间

动态规划
class Solution {
public:
    int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
        sort(arr2.begin(),arr2.end());//对arr2排序以进行二分查找
        unordered_map<int,int> dp;//dp数组
        dp[-1]=0;//迭代初始值
        for(int i=0;i<arr1.size();++i)//遍历arr1中的每个值,使得0~i有序
        {
            unordered_map<int,int> next;//下一个dp数组
            for(auto&it:dp)//遍历上层数组,进行状态转移
            {
                if(arr1[i]>it.first)//如果本来就有序了,可以不替换
                    next[arr1[i]]=next.count(arr1[i])?min(next[arr1[i]],it.second):it.second;
                auto iter=upper_bound(arr2.begin(),arr2.end(),it.first);//二分找到最优替换
                if(iter==arr2.end())//没找到,跳过
                    continue;
                    //替换
                next[*iter]=next.count(*iter)?min(next[*iter],it.second+1):it.second+1;
            }
            swap(dp,next);//滚动
        }
        int ans=INT_MAX;
        for(auto&it:dp)//遍历最后一个dp数组找到最优解
            ans=min(ans,it.second);//找最小操作数
        return ans==INT_MAX?-1:ans;
    }
};

二. 记忆化搜索

dp[i][j]为用j及之前的元素,使得前i个数数组递增的最小操作次数

class Solution {
public:
    int dp[2001][2001] = {};
    long dfs(vector<int>& a1, vector<int>& a2, int i1, int i2, int prev) {
        if (i1 >= a1.size()) return 0;//边界条件
        i2 = upper_bound(begin(a2) + i2, end(a2), prev) - begin(a2);//下一个满足条件的值
        if(dp[i1][i2]) return dp[i1][i2];//对应值存在,直接返回
        long r1 = INT_MAX, r2 = INT_MAX;
        if (i2 < a2.size()) //进行替换,操作数加一并移到下一位,替换数也后移
            r2 = 1 + dfs(a1, a2, i1 + 1, i2 + 1, a2[i2]); // replace with smallest      
        if (prev < a1[i1]) //上一个值小于当前值,不用替换
            r1 = dfs(a1, a2, i1 + 1, i2, a1[i1]); // keep
        return dp[i1][i2] = min(r1, r2);//备忘录记录
    }
    int makeArrayIncreasing(vector<int>& arr1, vector<int>& arr2) {
        sort(begin(arr2), end(arr2));//排序
        int res = dfs(arr1, arr2, 0, 0, INT_MIN);
        return res == INT_MAX ? -1 : res;
    }
};

posted @ 2023-04-21 00:50  失控D大白兔  阅读(54)  评论(0编辑  收藏  举报