PAT甲级题分类汇编——理论
本文为PAT甲级分类汇编系列文章。
理论这一类,是让我觉得特别尴尬的题,纯粹是为了考数据结构而考数据结构。看那Author一栏清一色的某老师,就知道教数据结构的老师的思路就是和别人不一样。
题号 | 标题 | 分数 | 大意 | Author |
1051 | Pop Sequence | 25 | 判断一个序列是否是pop序列 | CHEN, Yue |
1052 | Linked List Sorting | 25 | 链表排序 | CHEN, Yue |
1057 | Stack | 30 | 一个有中位数功能的stack | CHEN, Yue |
1074 | Reversing Linked List | 25 | 分段逆转链表 | CHEN, Yue |
1089 | Insert or Merge | 25 | 判断插入排序或归并排序 | CHEN, Yue |
1097 | Deduplication on a Linked List | 25 | 链表去重 | CHEN, Yue |
1098 | Insertion or Heap Sort | 25 | 判断插入排序或堆排序 | CHEN, Yue |
好几道题在上MOOC的时候就在数据结构题集里面做过,有1051、1074和1089。还有1098,是之前做merge那道的时候想一并做的,但后来因为merge花了太多时间就跳过了。
这次讲1074、1098、1051和1057 4道题。没错,不按题号顺序。
1074:
这可能是我做的第一道PAT题,也可能是第一道卡住的题。其实,所有用5位整数来模拟链表的题,好像都必须创建100000大小的数组,直接寻址才能跑进时间限制, std::map 是不行的。不过呢,如果我一开始是用C做的,肯定就直接建数组了,也就没有后面的问题了。
这道题的讲解在数据结构课程里有。
因为是早期作品,所以代码长得比较尴尬,懒得改了,我想睡觉。
- #include <iostream>
- #include <vector>
- #include <utility>
- #include <algorithm>
- #include <type_traits>
- #include <stack>
- #include <iomanip>
- struct Node
- {
- int address;
- int data;
- int next;
- };
- int main(int argc, char const *argv[])
- {
- int first, n, k;
- std::cin >> first >> n >> k;
- std::vector<Node> data(n);
- for (int i = ; i != n; ++i)
- std::cin >> data[i].address >> data[i].data >> data[i].next;
- decltype(data) list;
- int address = first;
- for (int i = ; i != n; ++i)
- {
- auto pos = std::find_if(std::begin(data), std::end(data), [address](const Node& node) {
- return node.address == address;
- });
- list.emplace_back(*pos);
- address = pos->next;
- if (address == -)
- break;
- }
- n = list.size();
- auto begin = list.begin();
- auto end = begin + (list.end() - begin) / k * k;
- for (; begin != end; begin += k)
- {
- std::stack<Node> stack;
- for (auto iter = begin; iter != begin + k; ++iter)
- stack.push(*iter);
- for (auto iter = begin; iter != begin + k; ++iter)
- {
- *iter = stack.top();
- stack.pop();
- }
- }
- for (int i = ; i != n - ; ++i)
- list[i].next = list[i + ].address;
- list[n - ].next = -;
- std::cout << std::setfill('');
- for (auto& node : list)
- {
- std::cout << std::setw() << node.address << ' ';
- std::cout << std::setw() << node.data << ' ';
- if (node.next == -)
- {
- std::cout << "-1";
- break;
- }
- std::cout << std::setw() << node.next << std::endl;
- }
- return ;
- }
1098:
它的兄弟题目1089在数据结构课程中讲过,包括这道题中也用到的判断插入排序的方法。
- #include <iostream>
- #include <vector>
- #include <algorithm>
- int main()
- {
- int size;
- std::cin >> size;
- std::vector<int> original(size);
- std::vector<int> partial(size);
- for (auto& i : original)
- std::cin >> i;
- for (auto& i : partial)
- std::cin >> i;
- int cur = ;
- for (; cur != size && partial[cur] >= partial[cur - ]; ++cur)
- ;
- bool insertion = true;
- for (auto i = cur; i != size; ++i)
- if (partial[i] != original[i])
- {
- insertion = false;
- break;
- }
- if (insertion)
- {
- std::cout << "Insertion Sort" << std::endl;
- int insert = partial[cur];
- for (--cur; cur >= ; --cur)
- if (partial[cur] > insert)
- partial[cur + ] = partial[cur];
- else
- break;
- partial[cur + ] = insert;
- }
- else
- {
- std::cout << "Heap Sort" << std::endl;
- int cur = size - ;
- auto top = partial[];
- for (; cur >= ; --cur)
- if (partial[cur] <= top)
- break;
- if (cur >= )
- {
- std::pop_heap(partial.begin(), partial.begin() + cur + );
- partial[cur] = top;
- }
- }
- auto end = partial.end() - ;
- for (auto iter = partial.begin(); iter != end; ++iter)
- std::cout << *iter << ' ';
- std::cout << *end;
- }
其实这道还要稍微简单一点,因为那道题要自己写merge,这道题的heap可以用标准库。<algorithm> 提供了 std::pop_heap 等函数用于堆操作。至于查看堆顶元素,把起始迭代器解引用就好,标准库没有给。
BTW,建堆的操作是O(N)的,证明起来挺组合数学的。
还有呢,我感觉这两道题贼尴尬。
1051:
判断一个序列是不是一个stack中pop出来的序列。
- #include <iostream>
- #include <vector>
- #include <stack>
- class Stack : public std::stack<int>
- {
- using super = std::stack<int>;
- public:
- explicit Stack(int _cap)
- : capacity_(_cap)
- {
- ;
- }
- void push(const int& _data)
- {
- if (size() == capacity_)
- throw ;
- super::push(_data);
- }
- private:
- int capacity_;
- };
- void check(int _cap, const std::vector<int>& _data)
- {
- Stack stack(_cap);
- int pushed = ;
- for (auto i : _data)
- {
- if (stack.empty())
- {
- stack.push(++pushed);
- }
- if (stack.top() < i)
- {
- for (int j = pushed + ; j <= i; ++j)
- stack.push(j);
- pushed = i;
- }
- if (stack.top() == i)
- {
- stack.pop();
- continue;
- }
- if (stack.top() > i)
- throw ;
- }
- }
- int main()
- {
- int m, n, k;
- std::cin >> m >> n >> k;
- std::vector<int> data(n);
- for (int i = ; i != k; ++i)
- try
- {
- for (int j = ; j != n; ++j)
- std::cin >> data[j];
- check(m, data);
- std::cout << "YES" << std::endl;
- }
- catch(...)
- {
- std::cout << "NO" << std::endl;
- }
- return ;
- }
题目不难,这里把代码贴出来,是想作个错误示范。算法本身是正确的,能AC,但是设计是不好的:标准库中的容器类不是用来作基类的,因为其析构函数非虚,我写的 Stack 类直接继承 std::stack<int> 是不好的,尽管在这个类设计中,子类的subclass不需要析构,而且整个程序没有作什么派生类到基类的指针转换。我的本意是想实现adapter,为了偷懒就直接公有继承,理应在 Stack 类中包装一个 std::stack<int> 实例。在工程中,公有继承标准库容器就是错误的。
1057:
这次不按题号讲,就是因为这道题要压轴。
初看,不就一个stack和中位数吗,两个我都会写,放到一起我也会写。然后我就写了第一个版本,做了一个 std::stack<int> 的adapter,提供了push、pop、median操作,其中median是把stack拷贝、排序、寻址中位数。感觉很好,样例也过了,想着一遍AC。
- #include <iostream>
- #include <string>
- #include <vector>
- #include <algorithm>
- class Stack
- {
- public:
- Stack() = default;
- void push(int i)
- {
- stack.push_back(i);
- }
- void pop()
- {
- if (stack.empty())
- std::cout << "Invalid";
- else
- {
- std::cout << stack.back();
- stack.pop_back();
- }
- std::cout << std::endl;
- }
- void median()
- {
- if (stack.empty())
- std::cout << "Invalid";
- else
- {
- auto temp = stack;
- std::sort(temp.begin(), temp.end());
- int m = ;
- if (temp.size() % )
- m = temp[(temp.size() - ) / ];
- else
- m = temp[temp.size() / - ];
- std::cout << m;
- }
- std::cout << std::endl;
- }
- private:
- std::vector<int> stack;
- };
- int main()
- {
- int count;
- std::cin >> count;
- Stack stack;
- for (int i = ; i != count; ++i)
- {
- std::string instr;
- std::cin >> instr;
- if (instr == "Push")
- {
- int i;
- std::cin >> i;
- stack.push(i);
- }
- else if (instr == "Pop")
- {
- stack.pop();
- }
- else
- {
- stack.median();
- }
- }
- }
然而并没有。5个case,3个超时。
是 std::cin 读取字符串太慢了吗?换成C的输入输出,还有2个case超时。
然后我想到输入数据有个比较小的范围是有用的,就建了个数组,写了个乱七八糟的访问控制来防止每次遍历都把每个元素访问一遍。没用,超时。
我意识到线性结构不能解决这个问题,不然也对不起这30分了。于是我就想到用 std::map 来存储。先放代码:
- #include <iostream>
- #include <string>
- #include <stack>
- #include <map>
- class Median
- {
- public:
- Median() = default;
- int get()
- {
- return median;
- }
- void insert(int i)
- {
- if (median_count == )
- {
- median = i;
- median_count = ;
- }
- else if (i < median)
- ++small[i], ++small_count;
- else if (i > median)
- ++large[i], ++large_count;
- else
- ++median_count;
- adjust();
- }
- void erase(int i)
- {
- if (i < median)
- {
- --small[i], --small_count;
- if (small[i] == )
- small.erase(i);
- }
- else if (i > median)
- {
- --large[i], --large_count;
- if (large[i] == )
- large.erase(i);
- }
- else
- --median_count;
- adjust();
- }
- private:
- std::map<int, int, std::greater<int>> small;
- int small_count = ;
- std::map<int, int> large;
- int large_count = ;
- int median;
- int median_count = ;
- void small_to_median()
- {
- median = small.begin()->first;
- median_count = small.begin()->second;
- small.erase(small.begin());
- small_count -= median_count;
- }
- void large_to_median()
- {
- median = large.begin()->first;
- median_count = large.begin()->second;
- large.erase(large.begin());
- large_count -= median_count;
- }
- void median_to_small()
- {
- small[median] = median_count;
- small_count += median_count;
- }
- void median_to_large()
- {
- large[median] = median_count;
- large_count += median_count;
- }
- void adjust()
- {
- if (median_count == )
- {
- if (small_count < large_count)
- large_to_median();
- else if (small_count)
- small_to_median();
- }
- else if (small_count + median_count < large_count)
- {
- median_to_small();
- large_to_median();
- }
- else if (small_count >= median_count + large_count)
- {
- median_to_large();
- small_to_median();
- }
- }
- };
- class Stack
- {
- public:
- Stack() = default;
- void push(int i)
- {
- stack_.push(i);
- median_.insert(i);
- }
- void pop()
- {
- if (stack_.empty())
- std::cout << "Invalid";
- else
- {
- int i = stack_.top();
- stack_.pop();
- std::cout << i;
- median_.erase(i);
- }
- std::cout << std::endl;
- }
- void median()
- {
- if (stack_.empty())
- std::cout << "Invalid";
- else
- {
- std::cout << median_.get();
- }
- std::cout << std::endl;
- }
- private:
- std::stack<int> stack_;
- Median median_;
- };
- int main()
- {
- int count;
- std::cin >> count;
- Stack stack;
- for (int i = ; i != count; ++i)
- {
- std::string instr;
- std::cin >> instr;
- if (instr == "Push")
- {
- int i;
- std::cin >> i;
- stack.push(i);
- }
- else if (instr == "Pop")
- stack.pop();
- else
- stack.median();
- }
- }
这个算法挺复杂的。客户维护一个 Stack 实例,它维护一个 Median 实例,两个都是我自己写的类。Median 中包含两个 std::map<int, int> 实例,分别储存比中位数小的和比中位数大的数。核心算法是 Median::adjust() ,它通过调整两边内容,维护中位数的正确性。不想细说了,看代码吧,只涉及到 std::map 的一些基本操作。
因为两次调整之间只有一个操作,所以可以保证调整一次就好了。
值得吐槽的一点是,C++的输入输出在这道题中比C慢了100ms。然而,算法不对,输入输出再快,也要超时。PAT好像没什么卡输入输出时间的题,毕竟世上除了C和C++还有好多编程语言,要考虑它们的感受。
写完了发现其他博客里没有用这么烦的方法,好像用了什么树状数组,我不会。
- try
- {
- learn_queue.push("树状数组");
- }
- catch(...)
- {
- std::cout << "快去睡觉" << std::endl;
- }
- --------------------------------------------------------------------------------
- 快去睡觉
- Program exited with code ...
正好提到树了,下一篇就写树吧。
PAT甲级题分类汇编——理论的更多相关文章
- PAT甲级题分类汇编——杂项
本文为PAT甲级分类汇编系列文章. 集合.散列.数学.算法,这几类的题目都比较少,放到一起讲. 题号 标题 分数 大意 类型 1063 Set Similarity 25 集合相似度 集合 1067 ...
- PAT甲级题分类汇编——图
本文为PAT甲级分类汇编系列文章. 图,就是层序遍历和Dijkstra这一套,#include<queue> 是必须的. 题号 标题 分数 大意 时间 1072 Gas Station 3 ...
- PAT甲级题分类汇编——树
本文为PAT甲级分类汇编系列文章. AVL树好难!(其实还好啦~) 我本来想着今天应该做不完树了,没想到电脑里有一份讲义,PPT和源代码都有,就一遍复习一遍抄码了一遍,更没想到的是编译一遍通过,再没想 ...
- PAT甲级题分类汇编——排序
本文为PAT甲级分类汇编系列文章. 排序题,就是以排序算法为主的题.纯排序,用 std::sort 就能解决的那种,20分都算不上,只能放在乙级,甲级的排序题要么是排序的规则复杂,要么是排完序还要做点 ...
- PAT甲级题分类汇编——计算
本文为PAT甲级分类汇编系列文章. 计算类,指以数学运算为主或为背景的题. 题号 标题 分数 大意 1058 A+B in Hogwarts 20 特殊进制加法 1059 Prime Factors ...
- PAT甲级题分类汇编——线性
本文为PAT甲级分类汇编系列文章. 线性类,指线性时间复杂度可以完成的题.在1051到1100中,有7道: 题号 标题 分数 大意 时间 1054 The Dominant Color 20 寻找出现 ...
- PAT甲级题分类汇编——序言
今天开个坑,分类整理PAT甲级题目(https://pintia.cn/problem-sets/994805342720868352/problems/type/7)中1051~1100部分.语言是 ...
- 【转载】【PAT】PAT甲级题型分类整理
最短路径 Emergency (25)-PAT甲级真题(Dijkstra算法) Public Bike Management (30)-PAT甲级真题(Dijkstra + DFS) Travel P ...
- PAT甲级题解分类byZlc
专题一 字符串处理 A1001 Format(20) #include<cstdio> int main () { ]; int a,b,sum; scanf ("%d %d& ...
随机推荐
- X-NUCA-ezphp记录
鸽了很久,还是记录一下 比赛的时候搞了很长时间,终于和mlt师傅搞出来了,竟然只有我们一队是预期解== <?php $files = scandir('./'); foreach($files ...
- 时间控件My97DatePicker事件监听及用法
引入js <script src="My97DatePicker/WdatePicker.js"></script> 应用 <input type=& ...
- useReducer代替Redux小案例-1(七)
使用useContext和useReducer是可以实现类似Redux的效果,并且一些简单的个人项目,完全可以用下面的方案代替Redux,这种做法要比Redux简单一些.因为useContext和us ...
- Jar hell问题以及解放方法
当一个类或一个资源文件存在多个jar中,就好存在jar hell问题 可以通过以下代码来诊断问题:
- 产品经理 写SQL
产品经理必备技能:写SQL - 云+社区 - 腾讯云https://cloud.tencent.com/developer/news/3177 产品经理学SQL(一)一个小时上手SQL | 人人都是产 ...
- springboot配置Filter的两种方法
一.使用注解1. 假设Filter类的路径为com.sanro.filter @Slf4j @WebFilter(filterName = "authFilter", urlPat ...
- Andorid SQLite数据库开发基础教程(2)
Andorid SQLite数据库开发基础教程(2) 数据库生成方式 数据库的生成有两种方式,一种是使用数据库管理工具生成的数据库,我们将此类数据库称为预设数据库,另一种是使用代码生成的数据库.
- 解决Wireshark安装Npcap组件失败
解决Wireshark安装Npcap组件失败 从Wireshark 3.0开始,Npcap取代Winpcap组件,成为Wireshark默认的网卡核心驱动.由于该组件属于驱动程序,所以安装时候容易 ...
- flutter Radio单选框
单选框,允许用户从一组中选择一个选项. import 'package:flutter/material.dart'; class RadioDemo extends StatefulWidget { ...
- Linux系统调优——Memory内存(二)
(1).查看Memory(内存)运行状态相关工具 1)free命令查看内存使用情况 [root@youxi1 ~]# free -m //-m选项,以MB为单位显示 total used free s ...