使数组和小于等于 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;
    }
};
posted @ 2023-08-09 19:04  失控D大白兔  阅读(28)  评论(0编辑  收藏  举报