You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

数组区间数字频次统计优化:大数组下内存过高问题求助

内存过高的原因分析

你当前代码的问题出在每个前缀位置都保存了一个完整的map<int,int>副本。对于105规模的数组,相当于存了105个独立的哈希表,内存开销是O(n*k)(k是数组中不同元素的数量),这种冗余存储直接导致内存爆炸。

优化方案

根据你的查询场景(在线/离线、查询次数多少),给你三种实用的优化方向:

方案一:离线查询 + 莫队算法(适合查询次数多的场景)

莫队算法通过对所有查询区间排序,用双指针动态维护当前区间的数字计数,避免了冗余存储。时间复杂度接近O((n+m)√n),内存仅需O(n+m),完全适配10^5规模的数组。

代码示例:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

const int MAXN = 1e5 + 5;
int a[MAXN], cnt[MAXN];
struct Query {
    int l, r, idx;
};
int block_size;

// 莫队排序规则,加奇偶优化减少指针移动次数
bool cmp(const Query& x, const Query& y) {
    if (x.l / block_size != y.l / block_size) {
        return x.l < y.l;
    }
    return (x.l / block_size % 2) ? (x.r > y.r) : (x.r < y.r);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    block_size = sqrt(n);
    
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    
    vector<Query> queries(m);
    for (int i = 0; i < m; i++) {
        cin >> queries[i].l >> queries[i].r;
        queries[i].idx = i;
    }
    sort(queries.begin(), queries.end(), cmp);
    
    int cur_l = 1, cur_r = 0;
    for (auto& q : queries) {
        // 调整双指针到当前查询区间
        while (cur_l > q.l) cnt[a[--cur_l]]++;
        while (cur_r < q.r) cnt[a[++cur_r]]++;
        while (cur_l < q.l) cnt[a[cur_l++]]--;
        while (cur_r > q.r) cnt[a[cur_r--]]--;
        
        // 输出当前查询结果
        cout << "Query " << q.idx + 1 << ":\n";
        // 如果元素数值范围大,先做离散化再用数组存cnt
        for (int num = 1; num < MAXN; num++) {
            if (cnt[num] > 0) {
                cout << num << ": " << cnt[num] << '\n';
            }
        }
        cout << '\n';
    }
    return 0;
}

如果数组元素数值过大(超过1e5),先对元素做离散化处理,把数值映射到1~k(k为不同元素数量),再用数组存储计数即可。

方案二:前缀数组+离散化(适合元素可离散化、查询次数少的场景)

如果数组元素可以离散化,我们为每个离散后的数值维护一个前缀和数组。查询时直接用前缀[r][x] - 前缀[l-1][x]得到x在区间内的出现次数,内存开销仅为O(n + k),远低于原代码。

代码示例:

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    vector<int> a = {1, 2, 2, 3, 4, 5, 5, 5, 6, 7};
    int n = a.size();
    
    // 第一步:离散化数组元素
    vector<int> sorted_unique = a;
    sort(sorted_unique.begin(), sorted_unique.end());
    sorted_unique.erase(unique(sorted_unique.begin(), sorted_unique.end()), sorted_unique.end());
    map<int, int> num_to_idx;
    for (int i = 0; i < sorted_unique.size(); i++) {
        num_to_idx[sorted_unique[i]] = i;
    }
    int k = sorted_unique.size();
    
    // 第二步:构建前缀和数组
    vector<vector<int>> prefix(n + 1, vector<int>(k, 0));
    for (int i = 1; i <= n; i++) {
        prefix[i] = prefix[i - 1];
        int idx = num_to_idx[a[i - 1]]; // 原数组是0基,对应前缀数组的1基索引
        prefix[i][idx]++;
    }
    
    // 查询区间[1,4](对应原数组的第1到第4个元素)
    int l = 1, r = 4;
    cout << "Query result:\n";
    for (int i = 0; i < k; i++) {
        int count = prefix[r][i] - prefix[l - 1][i];
        if (count > 0) {
            cout << sorted_unique[i] << ": " << count << '\n';
        }
    }
    return 0;
}

方案三:在线查询+主席树(适合必须在线处理的场景)

如果你的需求是必须在线处理查询(不能提前收集所有查询排序),可以用可持久化线段树(主席树)。每个版本的主席树对应数组前i个元素的计数,查询时用r版本的树减去l-1版本的树,就能得到区间内的元素计数。时间复杂度O(n log k + m log k),内存O(n log k),完全适配大数据量在线查询。

选择建议
  • 能离线处理优先选莫队算法,内存和时间效率都最优;
  • 元素数值范围小或可离散化、查询次数不多,选前缀数组+离散化;
  • 必须在线处理查询,选主席树。

内容的提问来源于stack exchange,提问作者Noproblem 29

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:05:38