使数组和小于等于 x 的最少时间
给你两个长度相等下标从 0 开始的整数数组 nums1 和 nums2 。每一秒,对于所有下标 0 <= i < nums1.length ,nums1[i] 的值都增加 nums2[i] 。操作完成后 ,你可以进行如下操作:
选择任一满足 0 <= i < nums1.length 的下标 i ,并使 nums1[i] = 0 。
同时给你一个整数 x 。请你返回使 nums1 中所有元素之和 小于等于 x 所需要的 最少时间,如果无法实现,那么返回 -1 。
1. 动态规划
分析
- 若存在实现的情况,则每个数最多移除一次,两次的情况必然更差
- 要使得当前移除最优,要让增加越多的越晚移除,所以要根据数组2决定移除顺序
- 根据升序结果,如果是已经决定选的,放到后面选才能使得最优(即如果已经决定选取数字,不用再考虑顺序)
- 根据升序遍历,对于每个数,只需确定选与不选即可
- 选取多少个数同样也是一个维度,最终确定为二维动态规划
- 由于指定选取个数的增加量都一样,这里动态规划值确定为最大消除量,避免对前面选取的数再进行计算
dp[i][j]表示从前i个数中选出j个数的最大减少量
class Solution {
public:
int minimumTime(vector<int>& nums1, vector<int>& nums2, int x) {
int n = nums1.size();
int sum = accumulate(nums1.begin(),nums1.end(),0);
int add = accumulate(nums2.begin(),nums2.end(),0);
vector<int> ids(n);//记录升序顺序
iota(ids.begin(), ids.end(), 0);
sort(ids.begin(), ids.end(), [&](const int i, const int j) {
return nums2[i] < nums2[j];
});
//如果是已经决定选的,一定要放到后面选,才能使得最优化
int dp[n+1][n+1];//dp[i][j]表示从前i个数中选出j个数的最大减少量
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){//升序遍历可选数,决定当前数选不选
int cur = ids[i-1];//当前可选数下标
for(int j=1;j<=i;j++)
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1]+nums1[cur]+nums2[cur]*j);
}
for(int i=0;i<=n;i++){
int sub = dp[n][i];//选择不同个数时的最大削减量
int remain = sum + add*i - sub;
if(remain<=x) return i;
}
return -1;
}
};
2. 一维优化
class Solution {
public:
int minimumTime(vector<int> &nums1, vector<int> &nums2, int x) {
int n = nums1.size();
// 对下标数组排序,避免破坏 nums1 和 nums2 的对应关系
vector<int> ids(n);
iota(ids.begin(), ids.end(), 0);
sort(ids.begin(), ids.end(), [&](const int i, const int j) {
return nums2[i] < nums2[j];
});
vector<int> f(n + 1);
for (int i: ids) //遍历可选数
for (int j = n; j; j--) //右后往前遍历,避免覆盖
f[j] = max(f[j], f[j - 1] + nums1[i] + nums2[i] * j);
int s1 = accumulate(nums1.begin(), nums1.end(), 0);
int s2 = accumulate(nums2.begin(), nums2.end(), 0);
for (int t = 0; t <= n; t++)
if (s1 + s2 * t - f[t] <= x)
return t;
return -1;
}
};