如何解决从左到右遍历数组并收集尽可能多的数字
CSES 问题 (https://cses.fi/problemset/task/2216/)。
给定一个数组,其中包含 1…n 之间的每个数字恰好一次。您的任务是按递增顺序收集从 1 到 n 的数字。 在每一轮中,您从左到右遍历数组并收集尽可能多的数字。总回合数是多少? 约束条件:1≤n≤2⋅10^5
这是我的 C++ 代码:
int n,res=0;
cin>>n;
int arr[n];
set <int,greater <int>> lastEl;
for(int i=0; i<n; i++) {
cin>>arr[i];
auto it=lastEl.lower_bound(arr[i]);
if(it==lastEl.end()) res++;
else lastEl.erase(*it);
lastEl.insert(arr[i]);
}
cout<<res;
我遍历了一次数组。如果元素 arr[i] 小于之前的所有元素,那么我“打开”一个新序列,并将该元素保存为该序列中的最后一个元素。我将已打开序列的最后一个元素存储在集合中。如果 arr[i] 小于前面的一些元素,那么我取已经存在的具有最大最后一个元素(但小于 arr[i])的序列,并用 arr[i] 替换该序列的最后一个元素。 唉,它只适用于给定的三个测试中的两个测试,而对于第三个测试,输出比它应该的要少得多。我做错了什么?
解决方法
让我详细解释一下我的思考过程,以便您下次遇到相同类型的问题时更轻松。
首先,我在面对此类问题时经常犯的一个错误是模拟过程的冲动。问题陈述中提到的“模拟过程”是什么意思?该问题提到进行一轮以最大化按特定顺序增加的数字的集合。所以,你从 1
开始,找到它并看到下一个数字 2
不在它之外,即 2
不能与 1
在同一轮并形成递增的序列。因此,我们需要对 2
进行另一轮。现在我们发现,2
和 3
都可以在同一轮中收集,因为我们从左向右移动并按递增顺序获取数字。但是我们不能取 4
,因为它在 2
之前开始。最后,对于 4
和 5
,我们需要另一轮。这样一共进行了三轮。
现在,如果您以这种方式模拟过程,问题将变得非常容易解决。在第一轮中,您查找以 1
开头的构成递增序列的数字。在开始第二轮之前删除这些数字。你继续这样,直到你用完所有的数字。
但是模拟这个过程会导致时间复杂度无法通过问题陈述中提到的约束。所以,我们需要想出另一种方法,在不模拟整个过程的情况下给出相同的输出。
请注意,数字的位置在这里至关重要。为什么我们需要对 2
进行另一轮?因为它出现在 1
之前。我们不需要对 3
进行另一轮,因为它在 2
之后。同样,我们需要对 4
进行另一轮,因为它在 2
之前。
因此,在考虑每个数字时,我们只需要关心在顺序中排在它前面的数字的位置。在考虑2
时,我们看1
的位置? 1
是在 2
之前还是之后?它来了,我们不需要另一轮。但如果它来之前,我们将需要一个额外的回合。对于每个数字,我们会查看此条件并在必要时增加轮次计数。这样我们就可以在不模拟整个过程的情况下计算出总轮数。
#include <iostream>
#include <vector>
using namespace std;
int main(int argc,char const *argv[])
{
int n;
cin >> n;
vector <int> v(n + 1),pos(n + 1);
for(int i = 1; i <= n; ++i){
cin >> v[i];
pos[v[i]] = i;
}
int total_rounds = 1; // we'll always need at least one round because the input sequence will never be empty
for(int i = 2; i <= n; ++i){
if(pos[i] < pos[i - 1]) total_rounds++;
}
cout << total_rounds << '\n';
return 0;
}
下次当您遇到此类问题时,请暂停一段时间,并尝试控制您在代码中模拟该过程的冲动。几乎可以肯定,会有一些巧妙的观察可以让您获得最佳解决方案。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。