使数组严格递增
给你两个整数数组 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;
}
};