PAT乙级刷题总结

开始先说最重要心得体会:

写代码前,先在纸上写写画画,写下伪码,理清思路,别上来就敲代码,效率极低还易出现bug。


2019-12-12到2020-01-17,用C++刷完了PAT乙级95道题目,第682个加入满分行列。

题目链接:
PAT乙级真题

image

乙级真题排名

为每道题撰写了相应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 
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
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 
using namespace std;
//定义
set myset;
//插入元素
myset.insert(20);
//查找元素
set::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对出现的数字计数,读到某个值时直接:
mapp[v[i][j]]++;

对map使用[]索引值,若key不存在则会将该key添加,并调用value的默认值作为其值。int的默认值即0。所以可以使用map直接计数。

遍历map

遍历map语法如下:

  // show content:
  for (std::map::iterator it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

反向遍历

以map为例,反向遍历STL库容器:

for (map::reverse_iterator rit = countMap.rbegin(); rit!=countMap.rend(); ++rit){
        cout << rit->first << " " << rit->second << endl;
}

类型map::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 
cout << setw(5) << setfill('0') << i;

2、printf:

printf("%05d", i);

小数点后精确2位

1、cout:

#include 
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 
std::reverse(myvector.begin(),myvector.end());

提高效率

遍历一遍同时得到最大、最小值

用两组临时变量,记录最大、最小两组值,如果有新的最大/小产生,相应更新改组数据。

如: PAT乙级真题 1004 成绩排名 C++实现

cin、cout与scanf、printf

cincoutscanfprintf慢很多,但只需在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);依然超时,改成scanfprintf后就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.si2.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。

你可能感兴趣的:(PAT乙级刷题总结)