【反悔贪心】【优先队列】3049. 标记所有下标的最早秒数 II

本文涉及知识点

反悔贪心 堆(优先队列)
二分查找算法合集

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++**实现。

  • 36
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值