3048. 标记所有下标的最早秒数 I(补题 ,二分 正序 逆序)

文章描述了一种算法问题,给定两个整数数组nums和changeIndices,目标是在最短时间内通过减少nums中的元素并标记特定下标来标记所有下标。使用二分查找确定操作时机,分析了两种遍历顺序的方法。
摘要由CSDN通过智能技术生成

3048. 标记所有下标的最早秒数 I

给你两个下标从 1 开始的整数数组 nums 和 changeIndices ,数组的长度分别为 n 和 m 。

一开始,nums 中所有下标都是未标记的,你的任务是标记 nums 中 所有 下标。

从第 1 秒到第 m 秒(包括 第 m 秒),对于每一秒 s ,你可以执行以下操作 之一 :

  • 选择范围 [1, n] 中的一个下标 i ,并且将 nums[i] 减少 1 。
  • 如果 nums[changeIndices[s]] 等于 0 ,标记 下标 changeIndices[s] 。
  • 什么也不做。

请你返回范围 [1, m] 中的一个整数,表示最优操作下,标记 nums 中 所有 下标的 最早秒数 ,如果无法标记所有下标,返回 -1 。

示例 1:

输入:nums = [2,2,0], changeIndices = [2,2,2,2,3,2,2,1]
输出:8
解释:这个例子中,我们总共有 8 秒。按照以下操作标记所有下标:
第 1 秒:选择下标 1 ,将 nums[1] 减少 1 。nums 变为 [1,2,0] 。
第 2 秒:选择下标 1 ,将 nums[1] 减少 1 。nums 变为 [0,2,0] 。
第 3 秒:选择下标 2 ,将 nums[2] 减少 1 。nums 变为 [0,1,0] 。
第 4 秒:选择下标 2 ,将 nums[2] 减少 1 。nums 变为 [0,0,0] 。
第 5 秒,标​​​​​记 changeIndices[5] ,也就是标记下标 3 ,因为 nums[3] 等于 0 。
第 6 秒,标​​​​​记 changeIndices[6] ,也就是标记下标 2 ,因为 nums[2] 等于 0 。
第 7 秒,什么也不做。
第 8 秒,标记 changeIndices[8] ,也就是标记下标 1 ,因为 nums[1] 等于 0 。
现在所有下标已被标记。
最早可以在第 8 秒标记所有下标。
所以答案是 8 。

示例 2:

输入:nums = [1,3], changeIndices = [1,1,1,2,1,1,1]
输出:6
解释:这个例子中,我们总共有 7 秒。按照以下操作标记所有下标:
第 1 秒:选择下标 2 ,将 nums[2] 减少 1 。nums 变为 [1,2] 。
第 2 秒:选择下标 2 ,将 nums[2] 减少 1 。nums 变为 [1,1] 。
第 3 秒:选择下标 2 ,将 nums[2] 减少 1 。nums 变为 [1,0] 。
第 4 秒:标​​​​​记 changeIndices[4] ,也就是标记下标 2 ,因为 nums[2] 等于 0 。
第 5 秒:选择下标 1 ,将 nums[1] 减少 1 。nums 变为 [0,0] 。
第 6 秒:标​​​​​记 changeIndices[6] ,也就是标记下标 1 ,因为 nums[1] 等于 0 。
现在所有下标已被标记。
最早可以在第 6 秒标记所有下标。
所以答案是 6 。

示例 3:

Input: nums = [0,1], changeIndices = [2,2,2]
Output: -1
Explanation: 这个例子中,无法标记所有下标,因为下标 1 不在 changeIndices 中。
所以答案是 -1 。

提示:

  • 1 <= n == nums.length <= 2000
  • 0 <= nums[i] <= 109
  • 1 <= m == changeIndices.length <= 2000
  • 1 <= changeIndices[i] <= n

解析:

看了别人题解,自己做一下总结:

在对时间的大小进行确定的时候存在单调性,可以用二分进行搜索答案。

我们用二分确定最后一天。

正序遍历就是尽量确定每个nums的最后天,其他时间进行-1操作。

用一个cnt记录可以用来操作-1的次数。当不够cnt时,return false;

class Solution {
public:
    int earliestSecondToMarkIndices(vector<int> &nums, vector<int> &changeIndices) {
        int n = nums.size(), m = changeIndices.size();
        if (n > m) return -1;

        vector<int> last_t(n);
        auto check = [&](int mx) -> bool {
            ranges::fill(last_t, -1);
            for (int t = 0; t < mx; t++) {
                last_t[changeIndices[t] - 1] = t; // 课程的最后一天 
            }
            if (ranges::find(last_t, -1) != last_t.end()) { // 有课程没有考试时间
                return false;
            }

            int cnt = 0;
            for (int i = 0; i < mx; i++) {
                int idx = changeIndices[i] - 1;//位置 
                if (i == last_t[idx]) { // 考试
                    if (nums[idx] > cnt) { // 没时间复习
                        return false;
                    }
                    cnt -= nums[idx]; // 复习这门课程
                } else {
                    cnt++; // 留着后面用
                }
            }
            return true;
        };

        int left = n - 1, right = m + 1;
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            (check(mid) ? right : left) = mid;
        }
        return right > m ? -1 : right;
    }
};

逆序遍历:

记录其需要标记的数组的数目,和需要操作-1的次数。

从后面遍历,当遇到一个未被标记的nums时,用一个study记录需要操作的个数。

当已经不需要进行标记nums时,进行需要操作个数-1进行。

class Solution {
public:
    int earliestSecondToMarkIndices(vector<int> &nums, vector<int> &changeIndices) {
        int n = nums.size(), m = changeIndices.size();
        if (n > m) return -1;

        vector<int> done(n); // 避免反复创建和初始化数组
        auto check = [&](int mx) -> bool { // mx是独一无二的
            int exam = n, study = 0; // 表示 要nums的个数 
            for (int i = mx - 1; i >= 0 && study <= i + 1; i--) { // 要复习的天数不能太多
                int idx = changeIndices[i] - 1;
                if (done[idx] != mx) {
                    done[idx] = mx;
                    exam--; // 考试
                    study += nums[idx]; // 需要复习的天数
                } else if (study) {
                    study--; // 复习
                }
            }
            return exam == 0 && study == 0; // 考完了并且复习完了
        };

        int left = n - 1, right = m + 1;
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            (check(mid) ? right : left) = mid;
        }
        return right > m ? -1 : right;
    }
};

时间复杂度为:O(mlongm)

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值