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

简介: 【反悔贪心】【优先队列】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 门课程。

image.png


试图替换:如果已学课程自学用时小于当前课程的自学用时,则替换。

性质五:如果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++**实现。

相关文章
|
7月前
【每日一题Day118】LC1124表现良好的最长时间段 | 前缀和+单调栈/哈希表
【每日一题Day118】LC1124表现良好的最长时间段 | 前缀和+单调栈/哈希表
55 0
算法修炼Day52|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组
算法修炼Day52|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组
|
7月前
leetcode代码记录(最长连续递增序列
leetcode代码记录(最长连续递增序列
41 2
|
7月前
|
索引
leetcode代码记录(最长重复子数组
leetcode代码记录(最长重复子数组
43 0
|
7月前
leetcode代码记录(长度最小的子数组
leetcode代码记录(长度最小的子数组
32 0
|
7月前
|
算法 测试技术 C#
[二分查找]LeetCode1964:找出到每个位置为止最长的有效障碍赛跑路线
[二分查找]LeetCode1964:找出到每个位置为止最长的有效障碍赛跑路线
|
7月前
代码随想录 Day44 动规12 LeetCode T300 最长递增子序列 T674 最长连续递增序列 T718 最长重复子数组
代码随想录 Day44 动规12 LeetCode T300 最长递增子序列 T674 最长连续递增序列 T718 最长重复子数组
64 0
|
7月前
|
算法 测试技术 C#
【滑动窗口】【二分查找】C++算法:和至少为 K 的最短子数组
【滑动窗口】【二分查找】C++算法:和至少为 K 的最短子数组
|
算法
代码随想录算法训练营第五十二天 | LeetCode 300. 最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组
代码随想录算法训练营第五十二天 | LeetCode 300. 最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组
50 1
|
机器学习/深度学习 人工智能 算法
代码随想录训练营day52| 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组...
代码随想录训练营day52| 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组...