开始先说最重要心得体会:
写代码前,先在纸上写写画画,写下伪码,理清思路,别上来就敲代码,效率极低还易出现bug。
2019-12-12到2020-01-17,用C++刷完了PAT乙级95道题目,第682个加入满分行列。
题目链接:
PAT乙级真题
为每道题撰写了相应CSDN博客:
Zhang35的CSDN个人主页
代码上传到了Github:
PAT-Basic-Level.git
每5道题是一套,分值为15、20、20、20、25,相当于一次PAT乙级考试。题目大都不难,因为乙级不考察数据结构,感觉乙级一道题,基本等于算法分析中的一句伪代码……
其中也有部分比较麻烦的题目,有些测试点很难通过,多亏了柳神的代码可以参考。她代码风格简洁、规范,博客整理的井井有条,对STL库的运用更是十分纯熟。柳神博客:
柳婼 の blog
柳婼的CSDN个人主页
后来每做一道题,无论对错,都会再搜柳神的代码,看有无可优化的地方。下面可能是我唯一一道写的比柳神简单点的:
PAT乙级真题 1087 有多少不同的值 C++实现(求出计算公式,1行代码搞定)
比较难的或代表性的有:
原创 PAT乙级真题 1025 反转链表
PAT乙级真题 1034 有理数四则运算 C++实现(两个int相乘结果应用long long存储)
PAT乙级真题 1035 插入与归并
PAT乙级真题 1040 有几个PAT C++实现
PAT乙级真题 1045 快速排序 C++实现
PAT乙级真题 1050 螺旋矩阵 C++实现
PAT乙级真题 1070 结绳 C++实现(类似求哈夫曼树)
PAT乙级真题 1089 狼人杀-简单版 C++实现(假设+遍历暴力求解)
遇到神坑悬而未解的有:
PAT乙级真题 1030 完美数列 C++实现(测试点4超时神坑)
PAT乙级真题 1035 插入与归并 C++实现(求解答:测试点4浮点错误,但不知道为啥还是得了满分)
一开始基本坚持每天1道,并到后面越刷越快,最多的一天是2020-01-14,刷了9道。收获是熟悉了网上刷题常见套路及C++ STL库的用法。下面总结一些常用操作和技巧。
常用函数
isPrime()
#include <cmath>
bool isPrime(int n){
//用pow(n, 0.5)会超时,sqrt效率大增
for (int i=2; i<=sqrt(n); i++){
if (n%i==0){
return false;
}
}
return true;
}
isNumber()
用sstream判断字符串是否为数字
#include<sstream>
bool isNumber(string s){
istringstream sin(s);
double test;
return sin >> test && sin.eof();
}
swap()
C++标准命名空间中有交换函数:
std::swap(T a, T b);
可以直接用于各种类型。
常用STL库操作
用vector排序
sort(v.begin(), v.end(), cmp);
用set判断相容性
set常见用法有:
#include <set>
using namespace std;
//定义
set<int> myset;
//插入元素
myset.insert(20);
//查找元素
set<int>::iterator it=myset.find(20);
//检查不存在某值
if (myset.find(value)==myset.end()){...}
//删除元素
myset.erase (it);
//删除所有元素
myset.clear();
//判断set是否为空
if (myset.empty()){...}
set是红黑树结构,近似平衡,插入、删除、查找操作的时间复杂度都是 O(logn),并且能自动排序。
适合判断相容性问题:PAT乙级真题 1064 朋友数
用map判断唯一
可使用map<int, int>对出现的数字计数,读到某个值时直接:
mapp[v[i][j]]++;
对map使用[]索引值,若key不存在则会将该key添加,并调用value的默认值作为其值。int的默认值即0。所以可以使用map直接计数。
遍历map
遍历map
语法如下:
// show content:
for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
反向遍历
以map为例,反向遍历STL库容器:
for (map<int, int>::reverse_iterator rit = countMap.rbegin(); rit!=countMap.rend(); ++rit){
cout << rit->first << " " << rit->second << endl;
}
类型map<int, int>::reverse_iterator
可以换成auto
(C++11特性)。
输入输出
用getline读带空格的一行字符串
读取一行带空格的字符串,需要用getline(cin, s);
cin >> s
只能读到空格前一个单词。
用scanf读string
用scanf替代cin时,无法直接读入string类型,可以先读进一个临时字符数组里,再把字符串数组赋值给字符串即可,如:
char temp[10];
scanf("%s", temp);
string str = temp;
打印定长数字
1、cout:
#include <iomanip>
cout << setw(5) << setfill('0') << i;
2、printf:
printf("%05d", i);
小数点后精确2位
1、cout:
#include <iomanip>
cout << fixed << setprecision(2) << i;
2、printf:
printf("%05d", i);
printf打印百分号%
用printf输出’%’,加转义字符’%'是不对的,正确做法是:
printf("%%");
字符串处理
使用cctype.h判断字符类型
使用c++中”cctype”(c中的“ctype.h”)能极大提高效率,包括对单个字符判断类型及大小写转换的函数:
isalnum() 如果参数是字母数字,即字母或数字,该函数返回true
isalpha() 如果参数是字母,该函数返回真
isblank() 如果参数是空格或水平制表符,该函数返回true
iscntrl() 如果参数是控制字符,该函数返回true
isdigit() 如果参数是数字(0~9),该函数返回true
isgraph() 如果参数是除空格之外的打印字符,该函数返回true
islower() 如果参数是小写字母,该函数返回true
isprint() 如果参数是打印字符(包括空格),该函数返回true
ispunct() 如果参数是标点符号,该函数返回true
isspace() 如果参数是标准空白字符,如空格、进纸、换行符、回车、水平制表符或者垂直制表符,该函数返回true
isupper() 如果参数是大写字母,该函数返回true
isxdigit() 如果参数是十六进制的数字,即0~9、a~f、A~F,该函数返回true
tolower() 如果参数是大写字符,则返回其小写,否则返回该参数
toupper() 如果参数是小写字母,则返回其大写,否则返回该参数
reverse()
倒置字符串,用法为:
#include <algorithm>
std::reverse(myvector.begin(),myvector.end());
提高效率
遍历一遍同时得到最大、最小值
用两组临时变量,记录最大、最小两组值,如果有新的最大/小产生,相应更新改组数据。
cin、cout与scanf、printf
cin
、cout
比scanf
、printf
慢很多,但只需在main函数第一行加上:
std::ios::sync_with_stdio(false);
就能快很多。因为C++为了兼容C,保证程序在使用了std::printf和std::cout的时候不发生混乱,将输出流绑到了一起。这行代码的作用便是取消iostream与stdio的默认关联同步,取消同步后的iostream的性能倍增,能与stdio相差无几。只要注意不要再混用iosteam和stdio即可,否则可能会出现预料之外的错误。
还能进一步提升效率,还可以再加上:
std::cin.tie(0);
但PAT乙级真题 1095中添加ios::sync_with_stdio(false);
和cin.tie(0);
依然超时,改成scanf
、printf
后就AC了。
str = str + "a"与str += "a"效率差距很大
前者产生了新的对象,再把结果返回;后者只涉及到对象的引用,不产生新对象。
拼接字符串最好使用str += "a"
,效率较高。
pow(n, 0.5)换成sqrt(n)
开方用pow(n, 0.5)有时会超时,sqrt效率大增。
排序传参使用引用传参
柳神说:“排序传参建议用引用传参,这样更快,虽然有时候不用引用传参也能通过,但还是尽量用,养成好习惯”。
代码如下:
bool cmp(const Info &i1, const Info &i2){
return i1.score==i2.score ? i1.s<i2.s : i1.score>i2.score;
}
用vector代替map
索引是数字的时候,可以用vector代替map,用空间换时间。毕竟限定时间宝贵,空间几乎无限。
用unordered_map代替map
map是用红黑树实现的,默认按key排序,使用unordered_map即不排序的map,能提高不少效率。
重要扩展知识
数值有效范围
char -128 ~ +127 (1 Byte)
short -32767 ~ + 32768 (2 Bytes) 3*10^4
unsigned short 0 ~ 65536 (2 Bytes) 6*10^4
int -2147483648 ~ +2147483647 (4 Bytes) 2*10^9
unsigned int 0 ~ 4294967295 (4 Bytes) 4*10^9
long == int
long long -9223372036854775808 ~ +9223372036854775807 (8 Bytes) 9*10^18
double 1.7 * 10^308 (8 Bytes)
位运算符
运算符有4种:
&(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。
可以用异或判断异号
//a,b异号或者有一个0直接算。
if ((int)(a^b)<0) { …… }
异或运算符是“相异”为1,“相同”为0:
- 若x^y>0 则二者同号(符号位为0)
- 若x^y<0 则二者异号(符号位为1)
如:
1 ^ 1 = 0 ^ 0 = 0
1 ^ 0 = 0 ^ 1 = 1
(这里的1和0是位,不是数值)
再例如,1 ^ -1 = -2。因为数据在内存中以补码表示(以8位为例):
1 = 00000001 --> 00000001(补码)
-1 = 10000001 --> 111111111(补码)
异或是在补码的基础上运算的,二者异或结果为11111110(补码)-->10000010 = -2。