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)