(519)

Java中的IO流分为节点流和处理流,节点流如FileOutputStream负责实际的数据读写,处理流如BufferedOutputStream提供效率保障。对象流用于对象的序列化和反序列化,而转换流如InputStreamReader和OutputStreamWriter在字节流和字符流之间转换。PrintWriter是具有自动行刷新功能的缓冲字符输出流,方便文本写入。

文件追加:

                FileOutputStream(File file,boolean append)

                FileOutputStream(String path,boolean append)

                                如果第二个参数为true,则指定为追加模式.此时创建文件流时如果指定的

                文件存在则内容会全部保留,新写入的内容会被追加到文件中

java将流分为两类 节点流与处理流:

 节点流:

                 又称为低级流,特点:实际连接程序与另一端的"管道",负责实际读写数据的流.

                 IO一定是建立在某个低级流的基础上进行的.

                文件流就是低级流,它们是实际连接程序与文件的管道,负责读写文件数据

处理流:

                又称为高级流,特点:不能独立存在,必须连接在其他流上,目的是当数据经过该

                流时对数据进行某种加工处理,简化我们的同等操作

                实际开发中我们经常串联一组高级流最终到某个低级流上,是的读写数据的过程中

                流水线式的操作对数据加工处理,这个过程也称为"流的连接" *

 缓冲流(Bufferde)

                java.io.BufferedInputStream和BufferedOutputStream

                缓冲流是一对高级流,在流连接中的作用:保证读写效率

注意:     在结束执行之后只需要关闭高级流即可,关闭高级流时通常会关闭其连接的流

                在程序中直接使用高级流的变量名调用方法即可

flush方法(清空缓存)

                是在接口Flushable上定义的,

        目的:强制将缓冲区中已经缓存的数据全部写出

                而OutputStream实现了该接口,java中所有的输出流都有flush方法.

                并非所有输出流的flush都有实质作用,而更多的是为了将flush动作传递下去,

                最终传递给缓冲流来真正完成清空缓冲区操作.

对象流(Object)

                java.io.ObjectOutputStream和ObjectInputStream

                对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化

                对象序列化:将一个java对象转换为一组可以被保存或传输的字节的过程

                对象输出流提供的方法: void writeObject(Object obj)

                可以将给定对象进行序列化,前提,该对象所属的类必须实现接口: java.io.Serializable

                否则序列化时会抛出异常:java.io.NotSerializableException

transient关键字(忽略执行)

                被该关键字修饰的属性在进行对象序列化是会被忽略。

                忽略不必要的属性可以达到对象序列化的"瘦身"

      目的:减少不必要的开销 持久化时可以降低磁盘空间的开销,网络传输时可以提高传输速度


字节与字符流

java将流按照读写的数据单位划分为字节与字符流

java.io.InputStream和OutputStream是字节流的超类

java.io.Reader和Writer是字符流的超类

字符流
最小读写单位是一个char(字符)

都是高级流,本质上底层最后还是要读写字节,只不过字符与字节的转换由字符流完成

可以直接写出字符串

转换流

转换流是字符流常用的一对实现类

作用:衔接字节与其他字符流

           将写出的字符按照指定的字符集转换为字节

他们在流链接中是重要的一环,但是实际开发中我们不会直接操作这对流 java.io.InputStreamReader和OutputStreamWriter

读:InputStreamReader

        int read()
        对于字符流而言,该方法用于读取1个字符,返回的int值对应的2进制
        是"低16位有效",实际返回的就是一个char。
        但是如果返回的int值为整数-1,依然表达流读取到了末尾

写:OutputStreamWriter

        int write()

        字符流可以直接写入字符串

作用:将写出的字符按照指定的字符集转换为字节

           因此,构造器创建时除了指明链接在哪个流上之外,还可以明确字符集

意义:转换流是唯一一对可以链接在字节流上的字符流,这样以来其他的字符流就可以通过

           链接转换流最终和字节流搭配使用

缓冲字符流、

 java.io.BufferedWriter与BufferedReader

作用: 块写文本数据保证写出效率的(内部默认有一个长度8192的char数组)

BufferedReader提供的独有方法:

String readLine() 方法

String line = br.readLine();

作用:按行读取字符串

当第一次调用readLine()方法时,缓冲字符输入流会一次性读取8192个字符(块读)

并存入内部的char数组中,然后从数组一个字符开始直到找到换行符位置,然后将

之前的内容转换为一个字符串返回.(看起来像是读取了一行字符串)

当第二次调用readLine()则直接从上次遇到换行符的位置开始再继续寻找下一个

换行符为止,将之间的内容转换为一个字符串返回

如果流读取到了末尾,此时该方法会返回null

        

java.io.PrintWriter

作用:具有自动行刷新功能的缓冲字符输出流

其内部总是链接BufferedWriter作为缓冲功能

缓冲流以块读写形式保证读写效率,并且可以按行读写字符串

PrintWriter提供了直接对文件进行写操作的构造器

PrintWriter(File file)

PrintWriter(String path)

        println方法

        PrintWriter第一个参数是一个输出流时,就支持再传入一个boolean 类型的参数,

        表示是否开启自动行刷新功能。

        当自动行刷新功能开启后,每当我们调用println方法时就会自动flush

        注:print方法并不会自动flush

        PrintWriter pw = new PrintWriter(bw,true);//开启自动行刷新

<think> 题目链接:https://codeforces.com/problemset/problem/519/B 题目大意:给出n个数字,然后给出n-1个数字(第一组),再给出n-2个数字(第二组)。第一组是原数组去掉一个元素,第二组是第一组再去掉一个元素。求被去掉的两个元素(按顺序,即第一个被去掉的是第一个答案,第二个被去掉的是第二个答案)。 方法1:使用求和法 原数组的和为s0,第一组数组的和为s1,则第一个被去掉的数为 s0 - s1。 第二组数组的和为s2,则第二个被去掉的数为 s1 - s2。 方法2:使用排序或哈希表(统计频率) 原数组为a,第一组数组为b,第二组数组为c。 第一个被去掉的元素:在a中出现但不在b中出现的元素,或者在a中比b中多出现一次的元素。 第二个被去掉的元素:在b中出现但不在c中出现的元素,或者在b中比c中多出现一次的元素。 考虑到可能有重复元素,所以使用统计频率的方法更可靠。 具体步骤: 1. 读入原数组a(长度为n),第一组数组b(长度为n-1),第二组数组c(长度为n-2)。 2. 分别统计a、b、c中每个元素的出现频率。 3. 第一个答案:遍历a的元素,找到在a中出现的次数比在b中多一次的元素(或者遍历b的元素,但注意a中可能有b没有的元素,所以遍历a更安全)。 具体:对于a中的每个元素x,如果a中x的频率比b中x的频率大1,则x就是第一个被去掉的。 但注意:因为b中可能没有x,所以当b中不存在x时,b中x的频率为0,此时a中x的频率应该是1(比0大1),所以成立。 4. 第二个答案:同理,遍历b的元素,找到在b中出现的次数比在c中多一次的元素。 但是需要注意:同一个元素可能在数组中出现多次,所以不能只用一个标记,必须统计频率。 另一种方法:由于题目保证答案唯一,我们可以用以下方法: 第一个答案 = 在数组a中出现但在数组b中少出现一次的元素(即a中比b中多一个的元素)。 第二个答案 = 在数组b中出现但在数组c中少出现一次的元素。 实现: 使用map来统计每个数组的元素频率。 但是注意:在统计第二个答案时,我们要在b中找比c中多一次的元素,而b中已经包含第一个答案,所以第一个答案在b中可能比a中少一次,但我们并不关心,因为第二个答案与第一个答案不同。 步骤: 1. 读入n,然后读入三个数组。 2. 分别用三个map统计a, b, c中每个元素的频率。 3. 遍历a中的每个元素,对于每个元素x,如果freq_a[x] != freq_b[x](实际上应该大1),则第一个答案就是x,并跳出(但注意可能有多个?题目保证唯一,所以找到一个即可?但是因为同一个元素出现多次,我们只需找到一个元素在a中频率比b中多1即可,但这样的元素只有一个,因为只去掉一个元素,所以整个数组a中只有一个元素会多一次。但实际上,如果有重复元素,那么可能有多个元素在a中的频率比b中多1?但是仔细分析:只去掉一个元素,所以整个数组a和b之间,只有被去掉的那个元素在a中的频率比b中多1,其他元素都相同。所以我们可以遍历a,找到第一个满足freq_a[x] > freq_b[x]的元素,然后输出并跳出?但是有可能这个元素在b中不存在,所以用freq_b[x]可能为0。 具体:我们可以这样: for (auto x: a) { if (freq_a[x] - freq_b[x] == 1) { first = x; break; } } 但是注意:在遍历时,同一个x可能出现多次,所以不能一找到就break,因为可能后面还有?不对,因为同一个x,我们通过频率比较,如果这个x在a中出现了k次,那么在b中出现了k-1次,那么当我们遍历到x时,就会满足条件。但是这样会多次检测到同一个x,所以我们可以记录下这个x,然后跳出循环?但是这样会重复记录同一个x多次。因此,我们可以遍历map,而不是遍历数组a。 改进:遍历频率映射: for (auto it : freq_a) { int x = it.first; if (it.second - freq_b[x] == 1) { first = x; break; } } 但是这里要注意:freq_b中可能没有x,所以我们需要检查x是否在freq_b中?或者我们初始化freq_b时,对于a中所有元素都初始化了?没有。所以我们可以这样:使用map的find方法,或者直接访问freq_b[x],如果x不在freq_b中,则返回0(因为我们使用map,默认值0)。 同样,第二个答案:遍历freq_b,找freq_b[x] - freq_c[x] == 1的元素。 但是注意:第一个答案也可能在b中出现(在c中不出现)吗?第二个答案只与b和c有关,所以这样找没有问题。 但是:第一个答案被去掉后,在b中就不存在了?不对,b是去掉第一个答案后的数组,所以第一个答案在b中出现的次数应该比a中少1(如果a中出现了多次,那么b中只少一次)。所以我们的方法正确。 但是:如果a中有重复元素,比如a=[2,2,3],b=[2,3],那么第一个被去掉的是2。那么freq_a[2]=2,freq_b[2]=1,所以2满足条件。而其他元素(3)在a和b中频率相等。 所以我们可以使用频率差的方法。 步骤: 1. 读入n,然后读入数组a(n个数),数组b(n-1个数),数组c(n-2个数)。 2. 定义三个map:freq_a, freq_b, freq_c。 3. 统计a中每个元素的频率,b中每个元素的频率,c中每个元素的频率。 4. 遍历freq_a,找到第一个满足 freq_a[x] - (freq_b.count(x)?freq_b[x]:0) == 1 的x,作为第一个答案。 5. 遍历freq_b,找到第一个满足 freq_b[x] - (freq_c.count(x)?freq_c[x]:0) == 1 的x,作为第二个答案。 但是注意:第一个答案可能有多个元素满足条件吗?题目保证唯一,所以只有一个元素会被去掉,所以只有一个元素满足这个条件。因此,我们找到第一个满足条件的x即可(但map遍历是随机的,所以最好直接遍历,然后输出找到的第一个,因为只有一个)。 但是,我们也可以不用map遍历,而是遍历数组a(但数组a有重复,所以可能重复找到同一个元素多次)。所以还是遍历map比较好。 但是注意:题目要求按顺序输出:先输出的第一个被去掉的,再输出第二个被去掉的。 所以,我们也可以这样: 第一个答案:遍历a,直到找到一个元素x,使得freq_a[x] > freq_b[x](并且我们每找到一个,就在freq_b[x]上加1?这样不行)。所以还是用map统计后直接查找。 代码实现: 注意:由于n最大10^5,所以用map统计不会超时(O(n log n))。 另外:也可以使用unordered_map(平均O(n))。 代码: 方法1:使用map(或unordered_map)统计频率。 但是:题目要求按顺序输出两个答案,所以先输出第一个被去掉的,再输出第二个。 步骤: 1. 读入数组a,b,c。 2. 统计频率: map<int, int> fa, fb, fc; for (int i=0; i<n; i++) fa[a[i]]++; for (int i=0; i<n-1; i++) fb[b[i]]++; for (int i=0; i<n-2; i++) fc[c[i]]++; 3. 然后: int first = 0, second = 0; for (auto it : fa) { int num = it.first; if (it.second != fb[num]) { // 因为只少一个,所以一定是fa[num] == fb[num] + 1 first = num; break; // 因为只有一个,所以找到就可以退出 } } for (auto it : fb) { int num = it.first; if (it.second != fc[num]) { second = num; break; } } 4. 输出 first 和 second。 但是,注意:在第一个循环中,我们遍历fa,然后比较fa[num]和fb[num](如果num不在fb中,fb[num]默认0)。但是当fa[num]不等于fb[num]时,我们直接取num作为第一个答案并跳出。因为只少一个元素,所以这样的元素只有一个。 但是:在第二个循环中,同样遍历fb,然后比较fb[num]和fc[num](如果num不在fc中,则fc[num]为0)。 但是:有没有可能第一个循环中,有多个元素满足fa[num]!=fb[num]?题目保证答案唯一,所以只有一个元素被去掉,所以只有一个元素满足fa[num]!=fb[num](即fa[num]比fb[num]大1,而其他都相等)。 但是,我们这样写可能会遇到问题:如果一个元素在a中不存在,但在fa中不会出现(因为我们遍历fa,所以不会出现这种情况)。所以正确。 但是:如果第一个答案在a中只出现一次,那么fb中这个元素就不存在(即fb[num]为0),此时fa[num]=1,fb[num]=0,满足不等。 所以,这个方法正确。 但是,有一个小问题:遍历map时,我们不知道顺序,但题目不要求顺序,只要输出那个被去掉的元素即可(因为答案唯一),所以任意顺序遍历map都可以。 但是,我们也可以不用map,而用数组求和法(更简单,且O(n)): long long s1 = 0, s2 = 0, s3 = 0; for (int i=0; i<n; i++) s1 += a[i]; for (int i=0; i<n-1; i++) s2 += b[i]; for (int i=0; i<n-2; i++) s3 += c[i]; int first = s1 - s2; int second = s2 - s3; 求和法更简单,且效率高(O(n)),但要注意数据范围:n最大10^5,元素最大10^9,那么s1最大10^14,所以用long long不会溢出(long long最大9e18)。 所以,推荐使用求和法。 但是,求和法有一个问题:如果数组中有重复元素,并且被去掉的元素是重复的,会不会影响?不会,因为求和法就是计算总和,去掉一个元素,那么两个数组的差就是这个元素。 所以,求和法完全正确。 因此,我们可以写两种方法,但这里推荐求和法(更简单高效)。 代码(求和法): #include <iostream> using namespace std; int main() { int n; cin >> n; long long a_sum = 0, b_sum = 0, c_sum = 0; int x; for (int i = 0; i < n; i++) { cin >> x; a_sum += x; } for (int i = 0; i < n-1; i++) { cin >> x; b_sum += x; } for (int i = 0; i < n-2; i++) { cin >> x; c_sum += x; } long long first = a_sum - b_sum; long long second = b_sum - c_sum; cout << first << endl; cout << second << endl; return 0; } 但是,题目要求输出两个答案,第一个答案和第二个答案。 但是,有没有可能多个不同的元素被去掉,但是和相同?题目没有说元素互不相同,所以有可能出现两个不同的元素和相同吗?题目要求去掉两个元素(但这两个元素可能相同),但是注意:在第一个数组中,去掉一个元素后得到第二个数组,再去掉一个元素得到第三个数组。所以两个被去掉的元素可能是相同的。 但是,用求和法:第一个答案 = a_sum - b_sum,第二个答案 = b_sum - c_sum。 如果两个被去掉的元素相同,比如a_sum - b_sum = X,b_sum - c_sum = X,那么输出两个X。 所以,求和法完全正确。 但是,题目中是否有说明元素互不相同?没有,所以可能重复。 因此,我们使用求和法。 但是,注意:题目要求输入输出格式,以及多个测试用例?看题目描述:输入第一行是n,然后第二行是n个整数(原数组),第三行是n-1个整数(第一组),第四行是n-2个整数(第二组)。 所以,我们只需要读入一次。 但是,题目中可能有多个测试用例?不,题目描述:The first line contains integer n (3≤n≤10^5) — the initial length of the array. 所以只有一个测试用例。 但是,我们再看题目:Input中第一行是n,所以代码按单组输入。 所以,最终代码: 注意:使用long long,因为元素最大10^9,n最大10^5,总和最大10^14。 代码: </think> 对于 Codeforces 519B 问题,我将提供两种优化解法:**求和法**(推荐)和**频率统计法**。原始问题需要找出两次删除操作中被移除的元素。 ### 解法一:求和法(O(n) 时间复杂度) 这是最优解法,利用数组和的差值直接计算被移除的元素: ```cpp #include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; long long a_sum = 0, b_sum = 0, c_sum = 0; int x; // 读取原始数组并求和 for (int i = 0; i < n; i++) { cin >> x; a_sum += x; } // 读取第一次删除后的数组并求和 for (int i = 0; i < n - 1; i++) { cin >> x; b_sum += x; } // 读取第二次删除后的数组并求和 for (int i = 0; i < n - 2; i++) { cin >> x; c_sum += x; } // 计算被移除的元素 long long first_removed = a_sum - b_sum; long long second_removed = b_sum - c_sum; cout << first_removed << "\n"; cout << second_removed << "\n"; return 0; } ``` #### 代码解释: 1. **输入处理**: - 读取数组大小 `n` - 使用 `long long` 防止总和溢出(元素值 ≤10⁹,n≤10⁵,总和最大 10¹⁴) 2. **求和计算**: - `a_sum`:原始数组总和 - `b_sum`:第一次删除后数组(n-1 个元素)总和 - `c_sum`:第二次删除后数组(n-2 个元素)总和 3. **差值计算**: - `first_removed = a_sum - b_sum`:第一个被删除的元素 - `second_removed = b_sum - c_sum`:第二个被删除的元素 4. **时间复杂度**:O(n),仅需三次线性遍历 ### 解法二:频率统计法(O(n log n) 时间复杂度) 使用哈希表统计元素频率,适合元素值范围大的情况: ```cpp #include <iostream> #include <vector> #include <unordered_map> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<int> a(n), b(n-1), c(n-2); unordered_map<int, int> freq_a, freq_b, freq_c; // 读取并统计原始数组频率 for (int i = 0; i < n; i++) { cin >> a[i]; freq_a[a[i]]++; } // 读取并统计第一次删除后数组频率 for (int i = 0; i < n - 1; i++) { cin >> b[i]; freq_b[b[i]]++; } // 读取并统计第二次删除后数组频率 for (int i = 0; i < n - 2; i++) { cin >> c[i]; freq_c[c[i]]++; } // 查找第一个被删除的元素 int first_removed = 0; for (int num : a) { if (freq_a[num] != freq_b[num]) { first_removed = num; break; } } // 查找第二个被删除的元素 int second_removed = 0; for (int num : b) { if (freq_b[num] != freq_c[num]) { second_removed = num; break; } } cout << first_removed << "\n"; cout << second_removed << "\n"; return 0; } ``` #### 代码解释: 1. **数据结构**: - 使用 `unordered_map` 存储元素频率(平均 O(1) 操作) - `vector` 存储输入数组 2. **频率统计**: - `freq_a`:原始数组频率 - `freq_b`:第一次删除后频率 - `freq_c`:第二次删除后频率 3. **查找被删元素**: - 第一个元素:在 `a` 中频率比 `b` 大 1 的元素 - 第二个元素:在 `b` 中频率比 `c` 大 1 的元素 4. **时间复杂度**:O(n log n)(哈希表操作均摊 O(1)) ### 两种解法对比 | 特性 | 求和法 | 频率统计法 | |--------------|---------------------------|--------------------------| | 时间复杂度 | O(n) | O(n)(均摊) | | 空间复杂度 | O(1) | O(n) | | 适用场景 | 推荐通用解法 | 元素范围极大时更稳定 | | 优势 | 代码简洁,内存占用少 | 不依赖数值计算 | | 注意事项 | 总和可能溢出(用long long)| 哈希冲突可能影响性能 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值