#P2315. 第1题-数据重删
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 1370
            Accepted: 369
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年8月21日-国内
                              
                      
          
 
- 
                        算法标签>模拟          
 
第1题-数据重删
题面描述:
数据重删是一种用于节约存储空间的技术,旨在识别和处理存储池内的重复数据块。其基本思路是,在多个重复的数据块中仅保留一份,通过地址指针指向这份唯一的数据。给定一组数据及其块的大小,需要设计一个方法判断当前数据块是否与之前的数据库重复。如果发现重复,需将该数据库删除,并在首次出现的数据库后增加重复计数,最终输出处理后的数据内容,格式为以空格分隔的数字,且最后没有空格。输入包括一个整数 N(数据个数)、一个整数 K(数据块大小)及 N 个整数(数据值),输出则为去除重复数据后的结果,例如对于输入 8 2 1 2 3 4 1 2 3 4,输出为 1 2 2 3 4 2。
题目思路
该题的要点是要统计出要多少个数据块,所以先将数组按照大小为k进行分段,并且对每一段进行计数,将相同的数据块一次输出。可以考虑使用map来对数据块计数。计数完毕后,再重新分段遍历数组,当map中存在该数据段时,一次性将其输出,并且将该数据段从map中删除,防止重复输出。
解题步骤
- 
数据输入与初始化:
- 读取输入数据的个数 N 和数据块的大小 K。
 - 将输入的 N 个整数存储在一个数组中。
 
 - 
数据分块:
- 使用一个循环将数据按大小为 K 进行分割,生成一个字符串形式的子数组。
 - 使用 
stringstream将每个子数组的元素连接为字符串,并存入一个vector<string>中。 
 - 
统计出现频次:
- 使用 
unordered_map来记录每个子数组字符串的出现次数。 - 另一个 
unordered_map用于存储子数组首次出现的索引,以便后续排序。 
 - 使用 
 - 
构建结果集合:
- 遍历统计结果,将子数组、频次和索引封装成三元组,并存储在一个向量中。
 - 根据首次出现的索引对三元组向量进行排序,确保输出顺序与输入一致。
 
 - 
格式化输出:
- 将处理后的结果拼接为最终输出字符串,确保格式正确,且没有多余的空格。
 
 
代码
python
n = int(input())  # 读取数组 a 的长度
k = int(input())  # 读取分割 a 的块大小
a = list(map(int, input().split()))  # 读取数组 a
parts = []  # 存储分割后的子数组
# 将数组 a 分成 k 份
left = 0
while left < n:
    right = min(left + k - 1, n - 1)  # 确定当前子数组的右边界
    parts.append(" ".join(map(str, a[left:right + 1])))  # 将当前子数组转为字符串并添加到 parts 列表中
    left = right + 1  # 更新左边界,准备处理下一个子数组
mp = {}  # 用于存储子数组出现的频次
idx = {}  # 用于存储子数组第一次出现的索引
for i in range(len(parts)):
    if parts[i] not in mp:  # 如果子数组第一次出现
        mp[parts[i]] = 1  # 将其频次设为 1
        idx[parts[i]] = i  # 记录其首次出现的索引
    else:
        mp[parts[i]] += 1  # 否则增加其频次
res = []  # 用于存储 (子数组, 频次, 索引) 的三元组
for key in mp:
    res.append((key, mp[key], idx[key]))  # 构建三元组并添加到 res 列表中
res.sort(key=lambda x: (x[2]))  # 根据索引对 res 列表进行排序
output = ""  # 初始化输出字符串
for x in res:
    output += x[0] + " " + str(x[1]) + " "  # 将每个三元组的内容拼接到输出字符串中
output = output[:-1]  # 去掉最后多余的空格
print(output)  # 输出结果
java
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        List<String> parts = new ArrayList<>();
        int left = 0;
        // 把数组切成长k份
        while (left < n) {
            int right = Math.min(left + k - 1, n - 1);
            StringBuilder sb = new StringBuilder();
            for (int i = left; i <= right; i++) {
                if (i > left) {
                    sb.append(" ");
                }
                sb.append(sc.nextInt());
            }
            parts.add(sb.toString());
            left = right + 1;
        } 
        // 计算每个部分的出现次数 以及 第一次出现的位置
        Map<String, Integer> mp = new HashMap<>();
        Map<String, Integer> idx = new HashMap<>();
        for (int i = 0; i < parts.size(); i++) {
            String part = parts.get(i);
            if (!mp.containsKey(part)) {
                mp.put(part, 1);
                idx.put(part, i);
            } else {
                mp.put(part, mp.get(part) + 1);
            }
        }
        // 按照第一次出现的位置排序
        List<List<Object>> res = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : mp.entrySet()) {
            List<Object> tuple = new ArrayList<>();
            tuple.add(entry.getKey());
            tuple.add(entry.getValue());
            tuple.add(idx.get(entry.getKey()));
            res.add(tuple);
        }
        res.sort(Comparator.comparingInt(list -> (int) list.get(2)));
        // 输出结果
        StringBuilder output = new StringBuilder();
        for (List<Object> tuple : res) {
            output.append(tuple.get(0)).append(" ").append(tuple.get(1)).append(" ");
        }
        System.out.println(output.toString().trim());
    }
}
C++
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <sstream>
using namespace std;
// 将字符串按给定的分隔符分割成字符串向量
vector<string> split(const string& str, char delimiter) {
    vector<string> result;
    stringstream ss(str);
    string token;
    while (getline(ss, token, delimiter)) {
        result.push_back(token);
    }
    return result;
}
int main() {
    int n, k;
    cin >> n >> k;  // 读取数组 a 的长度 n 和分割大小 k
    
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];  // 读取数组 a 的元素
    }
    
    vector<string> parts;  // 存储分割后的子数组
    int left = 0;
    while (left < n) {
        int right = min(left + k - 1, n - 1);  // 确定当前子数组的右边界
        stringstream ss;
        for (int i = left; i <= right; i++) {
            if (i > left) {
                ss << " ";  // 在子数组元素之间添加空格
            }
            ss << a[i];  // 将当前元素添加到子数组字符串中
        }
        parts.push_back(ss.str());  // 将当前子数组字符串添加到 parts 向量中
        left = right + 1;  // 更新左边界,准备处理下一个子数组
    }
    
    unordered_map<string, int> mp;  // 用于存储子数组出现的频次
    unordered_map<string, int> idx;  // 用于存储子数组第一次出现的索引
    for (int i = 0; i < parts.size(); i++) {
        if (mp.count(parts[i]) == 0) {  // 如果子数组第一次出现
            mp[parts[i]] = 1;  // 将其频次设为 1
            idx[parts[i]] = i;  // 记录其首次出现的索引
        } else {
            mp[parts[i]]++;  // 否则增加其频次
        }
    }
    
    // 构建 (子数组, 频次, 索引) 的三元组向量
    vector<tuple<string, int, int>> res;
    for (const auto& [key, value] : mp) {
        res.emplace_back(key, value, idx[key]);
    }
    
    // 根据索引对三元组向量进行排序
    sort(res.begin(), res.end(), [](const auto& a, const auto& b) {
        return get<2>(a) < get<2>(b);
    });
    
    string output;  // 初始化输出字符串
    for (const auto& t : res) {
        string part = get<0>(t);
        int count = get<1>(t);
        int _ = get<2>(t);  // 或者忽略这个变量如果不需要
        output += part + " " + to_string(count) + " ";  // 将每个三元组的内容拼接到输出字符串中
    }
    output.pop_back();  // 去掉最后多余的空格
    
    cout << output << endl;  // 输出结果
    return 0;
}
        视频讲解
数据重删是一种节约存储空间的技术,通常情况下,在数据存储池内是有很多重复的数据库。重删则是将这些重复的数据块找出并处理的技术。简单地说重删,就是将N份重复的数据快仅保留1份,并将N−1份数据的地址指针指向唯一的那一份。
我们输入一串存储的数据,用N表示数据个数,用K标识号数据库的大小,设计一个方法判断当前数据块是否和前面的数据库有重复,两个数据库内容完全一样则表示重复。如果重复则将这个数据库删除,并且在第一个出现数据库的后面增加重复数据的计数,输出经过重复之后的内容。
解答要求
时间限制:C/C++1000ms,其他语言 2000ms
内存限制:C/C++256MB, 其他语言512MB
输入
8输入数据的个数
2数据库的大小
1 2 3 4 1 2 3 4依次是数据值
输出
1 2 2 3 4 2输出结果为去除重复数据后的结果,输出结果最后没有空格,以数字结尾,输出内容不改变输入数据库的顺序
样例1
输入
8
2 
1 2 3 4 1 2 3 4
输出
1 2 2 3 4 2 
解释
总共8个数据,数据库的大小为2,按新窗口进行切片表示一个数据块,分别得到数据库为[1,2],[3,4],[1,2],[3,4]其中第一个数据块和第三个数据块相同,第二个数据块和第四个数据块相同,可以分别进行重测,重测之后数据库[1,2]的计数变为2,表示有两个相同的数据块[1,2],同理[3,4]d的数据块计数性变为2
样例2
输入
8
3
3 4 5 3 4 5 5 4 
输出
3 4 5 2 5 4 1