本文涉及知识点
反悔贪心 堆(优先队列)
二分查找算法合集
LeetCode3049. 标记所有下标的最早秒数 II
给你两个下标从 1 开始的整数数组 nums 和 changeIndices ,数组的长度分别为 n 和 m 。
一开始,nums 中所有下标都是未标记的,你的任务是标记 nums 中 所有 下标。
从第 1 秒到第 m 秒(包括 第 m 秒),对于每一秒 s ,你可以执行以下操作 之一 :
选择范围 [1, n] 中的一个下标 i ,并且将 nums[i] 减少 1 。
将 nums[changeIndices[s]] 设置成任意的 非负 整数。
选择范围 [1, n] 中的一个下标 i , 满足 nums[i] 等于 0, 并 标记 下标 i 。
什么也不做。
请你返回范围 [1, m] 中的一个整数,表示最优操作下,标记 nums 中 所有 下标的 最早秒数 ,如果无法标记所有下标,返回 -1 。
示例 1:
输入:nums = [3,2,3], changeIndices = [1,3,2,2,2,2,3]
输出:6
解释:这个例子中,我们总共有 7 秒。按照以下操作标记所有下标:
第 1 秒:将 nums[changeIndices[1]] 变为 0 。nums 变为 [0,2,3] 。
第 2 秒:将 nums[changeIndices[2]] 变为 0 。nums 变为 [0,2,0] 。
第 3 秒:将 nums[changeIndices[3]] 变为 0 。nums 变为 [0,0,0] 。
第 4 秒:标记下标 1 ,因为 nums[1] 等于 0 。
第 5 秒:标记下标 2 ,因为 nums[2] 等于 0 。
第 6 秒:标记下标 3 ,因为 nums[3] 等于 0 。
现在所有下标已被标记。
最早可以在第 6 秒标记所有下标。
所以答案是 6 。
示例 2:
输入:nums = [0,0,1,2], changeIndices = [1,2,1,2,1,2,1,2]
输出:7
解释:这个例子中,我们总共有 8 秒。按照以下操作标记所有下标:
第 1 秒:标记下标 1 ,因为 nums[1] 等于 0 。
第 2 秒:标记下标 2 ,因为 nums[2] 等于 0 。
第 3 秒:将 nums[4] 减少 1 。nums 变为 [0,0,1,1] 。
第 4 秒:将 nums[4] 减少 1 。nums 变为 [0,0,1,0] 。
第 5 秒:将 nums[3] 减少 1 。nums 变为 [0,0,0,0] 。
第 6 秒:标记下标 3 ,因为 nums[3] 等于 0 。
第 7 秒:标记下标 4 ,因为 nums[4] 等于 0 。
现在所有下标已被标记。
最早可以在第 7 秒标记所有下标。
所以答案是 7 。
示例 3:
输入:nums = [1,2,3], changeIndices = [1,2,3]
输出:-1
解释:这个例子中,无法标记所有下标,因为我们没有足够的秒数。
所以答案是 -1 。
提示:
1 <= n == nums.length <= 5000
0 <= nums[i] <= 109
1 <= m == changeIndices.length <= 5000
1 <= changeIndices[i] <= n
反悔贪心
changeIndices[i]都减一,下标转为从0开始。
本题 ⟺ \iff ⟺ 有n门课,自学需要nums[i]天;第i天有changeIndices[i]的网课,无论此课程难易,一天都可以学会。无论是自学还是上课,学习后都需要一天考试。
性质一:一门课,要么自学,要么上课,不会两者皆有。
性质二:上课必须在指定的那几天,所以可能导致有些天无事课干。比如:{2,0} {1,1,1,0},如果课程0 上网课,则需要5天。前3天有两天无事可做。自学随时可以进行,所以不会导致无事可干。
性质三:一门课第t1天和t2天有课,t1 < t2,t1学习不劣于t2学习。
性质四: mid1<mid2,如果mid1天能完成 学习,则mid2天一定能完成学习。我寻找第一个能完成学习的mid,即确定mid能学习后,还要在(left,mid]继续找。故用左开右闭空间的二分查找。
mid天能否完成学习
从后到前枚举第i天,符合以下两个条件,则试图学习:
一,当天的课程,自学超过1天。
二,第i天之前没有当天的课。
第i天及之后,最多可以学习canStudy = (mid-i)/2 门课程。
{ 学习当前课程 已学课程 < c a n S t u d y 贪心 试图替换已学课程 o t h e r 返回 \begin{cases} 学习当前课程 && 已学课程 < canStudy && 贪心 \\ 试图替换已学课程 && other && 返回 \\ \end{cases} {学习当前课程试图替换已学课程已学课程<canStudyother贪心返回
试图替换:如果已学课程自学用时小于当前课程的自学用时,则替换。
性质五:如果t1 < t2,t1学习,t2不学习引起的“无事可做”天数 <= t2学习,t1不学习 引起的“无事可做”天数。如果t1 自学的课程用时更多,则替换后的用时减少或不变。
下面来证明这样做是最优解:
一,如果nums[i] >=2 ,上课没有引起考试超过mid天,则上课一定优于自学。
二,某门课没有上课,说明它被淘汰了。
代码
核心代码
namespace NBinarySearch
{template<class INDEX_TYPE, class _Pr>INDEX_TYPE FindFrist(INDEX_TYPE left, INDEX_TYPE rightInclue, _Pr pr){while (rightInclue - left > 1){const auto mid = left + (rightInclue - left) / 2;if (pr(mid)){rightInclue = mid;}else{left = mid;}}return rightInclue;}template<class INDEX_TYPE, class _Pr>INDEX_TYPE FindEnd(INDEX_TYPE leftInclude, INDEX_TYPE right, _Pr pr){while (right - leftInclude > 1){const auto mid = leftInclude + (right - leftInclude) / 2;if (pr(mid)){leftInclude = mid;}else{right = mid;}}return leftInclude;}
}class Solution {
public:int earliestSecondToMarkIndices(vector<int>& nums, vector<int>& changeIndices) {vector<bool> vCanQuick(nums.size());for (auto& n : changeIndices){n--;if(vCanQuick[n]){n = -1;}else{vCanQuick[n] = true;}}auto Can = [&](int mid){std::priority_queue<int, vector<int>, std::greater<>> minHeap;for (int i = mid - 1; i >= 0; i--){const int iStudy = changeIndices[i];if ((-1 == iStudy) || (nums[iStudy] < 2)){continue;}if ((mid - i) / 2 > minHeap.size()){minHeap.emplace(nums[iStudy]);}else if (minHeap.size() && (minHeap.top() < nums[iStudy])){minHeap.pop();minHeap.emplace(nums[iStudy]);}}long long llNeed = std::accumulate(nums.begin(), nums.end(), 0LL) + nums.size();//自学考试总用时const int iRemain = mid - minHeap.size();//可以用于考试、自学的时间while (minHeap.size()) {llNeed -= minHeap.top();minHeap.pop();}return iRemain >= llNeed;};int iRet = NBinarySearch::FindFrist(-1, (int)changeIndices.size(), Can);return Can(iRet) ? iRet : -1;}
};
测试用例
template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{vector<int> nums, changeIndices;{Solution sln;nums = { 0,2,0,1 }, changeIndices = { 2,3,2,3,4,4,1,3 };auto res = sln.earliestSecondToMarkIndices(nums, changeIndices);Assert(6, res);}{Solution sln;nums = { 3, 2, 3 }, changeIndices = { 1, 3, 2, 2, 2, 2, 3 };auto res = sln.earliestSecondToMarkIndices(nums, changeIndices);Assert(6, res);}{Solution sln;nums = { 0,0,1,2 }, changeIndices = { 1,2,1,2,1,2,1,2 };auto res = sln.earliestSecondToMarkIndices(nums, changeIndices);Assert(7, res);}{Solution sln;nums = { 1,2,3 }, changeIndices = { 1,2,3 };auto res = sln.earliestSecondToMarkIndices(nums, changeIndices);Assert(-1, res);}
}
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快
速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关
下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。