#include <iostream> #include <vector> #include <map> #include <unordered_map> #define int long long using namespace std; const int N = 1e6 + 5; typedef pair<int,int> PII; signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin >> t; while(t --) { unordered_map<int,int> mp; int n; cin >> n; vector<int> a(n + 1, 0); vector<int> b(n + 2, 0); // 记录此时每个数后面有多少个不同的数(包括其本身) vector<int> st(N, 0); for(int i = 1; i <= n; i ++) cin >> a[i]; for(int i = n; i >= 1; i --) { b[i] = b[i + 1]; if(!mp[a[i]] && a[i] > 0) { mp[a[i]] ++; b[i] ++; } } mp.clear(); int ans = 0; for(int i = 1; i <= n; i ++) { if(!mp[a[i]]) { mp[a[i]] = i; } else { if(st[a[i]]) continue; if(mp[0] < i && mp[0] > mp[a[i]] && a[i] > 0) { ans += b[i + 1]; st[a[i]] = 1; } mp[a[i]] = i; } } cout << ans << '\n'; } return 0; }这段代码的时间复杂度是多少
时间: 2025-07-04 15:43:31 浏览: 14
<think>好的,我现在需要分析用户提供的这段C++代码的时间复杂度。用户之前询问过将map改为unordered_map后的时间复杂度,现在他们给出了一段具体代码,想了解这段代码的时间复杂度。
首先,我要通读代码,理解它的功能。代码的大致结构是处理多个测试用例,每个测试用例中处理一个数组a,计算某种结果ans。代码中使用了unordered_map,vector和一些循环结构。
接下来,我需要分步骤分析每个部分的时间复杂度。通常,时间复杂度由循环和嵌套循环决定,同时需要注意数据结构操作的时间。
代码的结构如下:
1. 主函数开始,读取测试用例数目t。
2. 每个测试用例中:
a. 创建unordered_map<int,int> mp。
b. 读取n,然后读取数组a(大小为n+1)。
c. 创建两个vector:b和st。
d. 第一个循环:从n到1遍历,填充b数组,使用mp记录出现过的a[i]。
e. 清空mp。
f. 第二个循环:从1到n遍历,处理条件,更新ans。
g. 输出ans。
现在,我需要分析每个部分的时间复杂度。
首先,测试用例的循环是t次,每个测试用例内部的时间复杂度是重点。假设每个测试用例的数据规模是n,那么总时间复杂度是t乘以每个测试用例的时间复杂度。
接下来看每个测试用例内的部分:
第一个循环:从i = n到1,循环次数是n次。循环内部的操作包括检查mp[a[i]]是否存在,以及可能的递增操作。由于使用的是unordered_map,每次的插入和查找操作平均时间复杂度是O(1),最坏情况是O(n),但通常假设哈希表良好,平均情况是O(1)。因此,这个循环的时间复杂度是O(n)。
然后,mp.clear()操作,这个操作的时间复杂度是O(mp.size())。在最坏情况下,如果所有元素都是唯一的,mp的大小是n,所以clear的时间复杂度是O(n)。不过,这可能因实现而异,但一般来说,清空哈希表的操作是线性时间。
第二个循环:同样是从i=1到n,循环次数n次。循环内部有对mp[a[i]]的查找和可能的赋值。同样,unordered_map的查找和插入平均是O(1)。此外,还有对st数组的访问,这是O(1)。因此,这个循环的时间复杂度也是O(n)。
所以,每个测试用例的时间复杂度是O(n) + O(n) = O(n)。加上外层的t次循环,总的时间复杂度是O(t * n)。
但需要考虑unordered_map在最坏情况下的表现,比如哈希冲突导致每次操作变成O(n)。不过,这种情况在实际应用中很少见,特别是当哈希函数良好时。因此,通常以平均情况分析,即O(t * n)。
另外,vector的初始化也需要考虑。例如,vector<int> st(N, 0);这里的N是1e6+5,所以每次测试用例都会初始化一个大小为1e6+5的vector。这在时间上可能是O(N),即O(1e6)次操作。如果t很大,比如1e5,那么总时间会是1e5 * 1e6 = 1e11,这显然无法接受。但是原代码中,st是在每个测试用例内部定义的,所以每个测试用例都会创建一个新的st,而N是1e6+5,因此每次初始化st的时间是O(N)。这可能会成为性能瓶颈。
不过,用户的问题是关于时间复杂度的,而时间复杂度通常关注输入规模相关的部分。如果N是常量(1e6+5),那么初始化st的时间是O(1),因为无论n是多少,st的大小固定为N。不过,实际上,如果N非常大(如1e6),每个测试用例的初始化时间可能成为实际运行时间的负担,但时间复杂度分析中,常量因素通常被忽略,所以总时间复杂度仍是O(t * n)。
但是,如果用户代码中的n可以达到1e5或更大,而t也是1e5,那么总的时间复杂度是O(t * n)可能会很高,比如1e10操作,这在实际中无法处理。但需要看具体的输入限制。不过用户没有给出输入范围,所以只能根据代码结构分析。
总结:
每个测试用例的时间复杂度为O(n)(两个O(n)的循环),加上st的初始化O(N),如果N是常量,则视为O(1)。因此,总时间复杂度为O(t * n)。但是,如果N是1e6,并且每个测试用例都初始化一个这样的vector,那实际上每个测试用例的时间是O(n + N)。如果t很大,比如t=1e4,而N=1e6,那么总时间是1e4 * (n + 1e6)。如果n比较小(比如n=1e3),那么总时间主要由1e4 * 1e6 = 1e10决定,这显然不行。但在时间复杂度分析中,通常假设所有操作都是关于输入规模的,而N是预定义的常量,所以可能被视为O(1)。但这里可能用户代码存在潜在的性能问题,尤其是当t很大时。
不过,用户的问题是关于时间复杂度的计算,所以应该按标准方式分析,即每个测试用例的时间复杂度是O(n)(两个循环),总的是O(t*n)。而st的初始化虽然是O(N),但N是常量,所以不影响时间复杂度的大O表示。不过,如果N非常大,并且在实际中需要考虑的话,可能需要指出这一点。但根据常规分析,时间复杂度应为O(t*n)。
另外,代码中有一个宏定义#define int long long,这可能影响空间占用,但不影响时间复杂度。
最后,用户可能想知道将map改为unordered_map后的时间复杂度,所以原代码中如果是map,第一个循环的时间是O(n log n),第二个循环也是O(n log n),总的是O(t * n log n)。但现在的代码使用unordered_map,所以两个循环的时间是O(n)平均情况,因此总时间复杂度是O(t*n)平均情况,但最坏情况下可能退化到O(t*n^2)如果哈希冲突严重。不过通常回答平均情况,所以总时间复杂度是O(t*n)。</think>以下是代码的时间复杂度分析:
### 代码结构分析
```cpp
#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
#define int long long
using namespace std;
const int N = 1e6 + 5;
typedef pair<int,int> PII;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t --) {
unordered_map<int,int> mp;
int n;
cin >> n;
vector<int> a(n + 1, 0);
vector<int> b(n + 2, 0);
vector<int> st(N, 0); // 注意:st 是固定大小(1e6+5)的静态数组
for(int i = 1; i <= n; i ++) cin >> a[i];
// 第一个循环:从后向前统计不同元素数量
for(int i = n; i >= 1; i --) {
b[i] = b[i + 1];
if(!mp[a[i]] && a[i] > 0) {
mp[a[i]] ++;
b[i] ++;
}
}
mp.clear();
int ans = 0;
// 第二个循环:从前向后处理条件
for(int i = 1; i <= n; i ++) {
if(!mp[a[i]]) {
mp[a[i]] = i;
} else {
if(st[a[i]]) continue;
if(mp[0] < i && mp[0] > mp[a[i]] && a[i] > 0) {
ans += b[i + 1];
st[a[i]] = 1;
}
mp[a[i]] = i;
}
}
cout << ans << '\n';
}
return 0;
}
```
---
### 时间复杂度分解
#### 1. **外层循环**
- **`while(t--)`**:执行 $t$ 次,每个测试用例独立处理。
#### 2. **内层操作**
每个测试用例包含以下关键步骤:
##### (1) **初始化**
- `vector<int> st(N, 0)`:时间复杂度为 $O(N)$,但 $N$ 是预定义的常量($10^6+5$),因此视为 $O(1)$。
- `vector<int> a(n+1, 0)` 和 `vector<int> b(n+2, 0)`:初始化时间复杂度为 $O(n)$。
##### (2) **第一个循环:从后向前统计不同元素**
- **循环次数**:$n$ 次。
- **操作内容**:
每次检查 `unordered_map` 中是否存在 `a[i]`,插入和查找操作的平均时间复杂度为 $O(1)$。
- **总时间复杂度**:$O(n)$。
##### (3) **第二个循环:从前向后处理条件**
- **循环次数**:$n$ 次。
- **操作内容**:
每次检查 `unordered_map` 中是否存在 `a[i]`,以及条件判断和更新操作,所有操作的平均时间复杂度为 $O(1)$。
- **总时间复杂度**:$O(n)$。
---
### 总时间复杂度
- **单测试用例**:$O(n) + O(n) + O(1) = O(n)$。
- **整体代码**:$t \times O(n) = O(t \cdot n)$。
---
### 关键影响因素
1. **哈希表性能**:
`unordered_map` 的插入和查找操作平均为 $O(1)$,但若哈希函数质量差或数据分布不均匀,最坏情况下可能退化为 $O(n)$,此时总时间复杂度会变为 $O(t \cdot n^2)$。
**实际应用中需确保哈希函数的高效性**。
2. **静态数组 `st` 的初始化**:
虽然 `st` 的大小为 $10^6+5$,但由于它是预先定义的常量,初始化时间被摊还为 $O(1)$,不影响整体时间复杂度。
---
### 对比 `map` 的版本
- 若将 `unordered_map` 改回 `map`(基于红黑树),则每个插入/查找操作的时间复杂度为 $O(\log n)$,总时间复杂度变为 $O(t \cdot n \log n)$。
- **优化效果**:
使用 `unordered_map` 将时间复杂度从 $O(t \cdot n \log n)$ 降低到 $O(t \cdot n)$(平均情况)。
---
### 总结
- **平均时间复杂度**:$O(t \cdot n)$。
- **最坏时间复杂度**:$O(t \cdot n^2)$(需避免哈希冲突)。
- **适用性**:在哈希函数高效、数据分布均匀的场景下性能更优。
阅读全文
相关推荐














