一、刷题notes
- 刷题要按照系统的进行,不要偷懒按题号或者其他方式乱做,以形成完整的知识体系
- 要自己有思路自己能实现,看懂别人的并没啥用,不要把宝贵时间浪费在纸上谈兵
- 总结相似题目的题解
二、all hail to you,c/c++
- 不要在程序中同时使用cout和printf,可能会出问题
- 文件扩展名:
C .c
C++ .cpp - main()主函数是一个程序的入口位置。整个程序从主函数开始执行,一个程序最多只能有一个主函数。
2.1 基本数据类型
2.1.1变量的定义
- 变量:在程序运行过程中其值可以改变的量。需要在定义后才能用
定义&赋初值
变量类型 变量名 [= 初值];
- 变量名需要满足:
- 不能是C语言标识符(例如if,while啥的)
- 变量名的第一个字符必须是字母或下划线
除第一个字符外的其他字符必须是字母、下划线或数字 - 区分大小写
2.1.2变量类型
- 整型
- int 型:题目要求绝对值10^9以内或者说32位整数
- long long型:10^18以内或64位整数
- long long型赋大于2^31-1的初值,需要在初值后加LL,否则会出现编程错误。
- 浮点型:推荐浮点型数据都用double类型
double 有效精度15 ~ 16位,float 有效精度6 ~ 7位
浮点型数据的精度由尾数的位数来决定
在计算机中,浮点数以科学计数法的方式来存储
-
字符型
- 字符常量统一使用ASCII/* ['æski] */码 进行编码
- 大写字母比小写字母小32
- a+‘a’-'A’把一个大写字母变成小写字母
- a+‘A’-'a’把一个小写字母变成大写字母(大写字母比小写字母小32
#include <stdio.h>
int main(){
char a = 'a', b = 'A';
printf("%c,%c", a - 32, b + 32);//A,a
return 0;
}
其中需要注意:
常用字符 | ASCII码 |
---|---|
0~9 | 48~57 |
A~Z | 65~90 |
a~z | 97~122 |
notice:小写字母比大写字母大32
- 引用:起个别的昵称
- &的两种用法
- 引用:别名
- 取地址运算符
- 注意:常量不可使用引用
2.1.4符号常量和const常量
常量:一旦确定其值后不能改变
- 宏定义(或宏替换):直接把对应的部分替换,然后再进行编译和运行
因此使用时把能加的地方都加上括号,防止改变运算顺序
#define 标识符 常量
- const常量(!定义常量时推荐使用
const 数据类型 变量名 = 常量;
2.2.2scanf和printf
字符串或者数组不需要加取地址符&
- scanf的%c格式可以读入空格和换行,即空格和换行可以作为字符的内容
- 除%c外,scanf对其他格式符(例如%d,%s),以空格和换行作为读入结束的标志
- double型变量,输入是%lf,输出是%f(开始是殷勤,之后就顺便了~
- 常用的输出格式
- %md
让不足m为的int型变量以m位右对齐输出,高位不足则用空格补足
int a = 1, b = 123;
printf("%2d\n", a);
printf("%2d\n", b);
1
123
- %0md
不足m位用0补齐,其他同上
int a = 1, b = 123;
printf("%02d\n", a);
printf("%02d\n", b);
01
123
- %.mf
浮点数保留m位小数输出(保留规则是四舍六入五成双(5前偶数舍去,奇数进位
#include <stdio.h>
int main(){
double d = 1.3456;
printf("%.0f\n", d);//1
printf("%.1f\n", d);//1.3
printf("%.2f\n", d);//1.35
printf("%.3f\n", d);//1.346
printf("%.4f\n", d);//1.3456
return 0;
}
2.2.3getchar和putchar输入输出字符
对象是单个字符,则可以识别换行符和空格
2.2.5typedef
用处:给复杂的数据类型起个外号
typedef 数据类型 别名;
2.2.6常用的math函数
- fabs(double x)
对double型变量取绝对值 - floor(double x) 向下取整
ceil(double x) 向上取整
double db1 = -1.1, db2 = 1.1;
printf("%.0f, %.0f\n", floor(db1), ceil(db1));//-2,-1
printf("%.0f, %.0f\n", floor(db2), ceil(db2));//1,2
- pow(double r, double p)
返回r^p - sqrt(double x)
返回x的算术平方根 - log(double x)
返回以自然数为底的x的对数 - sin(double x),cos(double x),tan)(double x)
可以把定义为精确值acos(-1.0) - asin(double x),acos(double x),atan(double x)
- round(double x)
对x四舍五入取整
#include <stdio.h>
#include <math.h>
int main(){
double db1 = round(3.40), db2 = round(3.50), db3 = round(3.60);
printf("%f,%f,%f\n%d,%d,%d", db1, db2, db3, (int)db1, (int)db2, (int)db3);
//3.000000,4.000000,4.000000
//3,4,4
return 0;
}
2.3.3switch语句
switch(表达式){
case 常量表达式1:
……
break;
case 常量表达式2:
……
break;
default:
……
}
- 默认两个case之间的内容全部是上一个case的内容,则无需加大括号
- break结束当前switch语句。若无,程序会从第一个匹配的case开始执行直到下面的全部语句执行完才退出switch
2.4.4break和continue
break:结束循环(讲究个一刀两断
continue:结束本轮循环
2.5数组
2.5.1一维数组
数组:相同数据类型的变量组成的数据集合
- 数组大小 必须是整数常量,不可以是变量
- 数组下标从0开始
例如长度为n的一维数组,下标范围为0—n-1 - 数组未初始化时,每个元素可能会是个随机数,并不一定默认为0
- 若数组过大(分界点大概10^6级别),则需把其定义在主函数外,否则会使程序异常退出。
因为函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。
2.5.4memset对数组中每一个元素赋相同的值
memset(数组名,值,sizeof(数组名));
- 需要加头文件string.h
- 按字节赋值.
建议初学时用来赋值0或-1(补码值全0或全1);
赋其他值时,推荐用fill()(速度比memset()慢)
#include<stdio.h>
#include<string.h>
int main(){
int a[5] = {1, 2, 3, 4, 5};
memset(a, 0, sizeof(a));
for(int i = 0; i < 5; i++)
printf("%d ", a[i]);
printf("\n");//0 0 0 0 0
memset(a, -1, sizeof(a));
for(int i = 0; i < 5; i++)
printf("%d ", a[i]);
printf("\n");//-1 -1 -1 -1 -1
memset(a, 1, sizeof(a));//0001 0001 0001 0001
for(int i = 0; i < 5; i++)
printf("%d ", a[i]);
printf("\n");//16843009 16843009 16843009 16843009 16843009
return 0;
}
2.5.5字符数组
- 字符数组的初始化
字符输在可通过字节赋值字符串来初始化(其他位置不可以这样赋值整个字符串 - 字符数组的输入输出
(1)scanf&printf
%c 可识别空格和换行作为内容
%s 用空格或换行作为结束标志
(2)getchar&putchar
对象是单个字符
(3)gets&puts
gets识别换行作为输入结束(同scanf %s
所以如果输入其他在用gets时,要先用getchar吸收换行符 - 字符数组的存放方式
空字符\0:表示存放的字符串的结尾。占用一个字符位
⇨开字符数组时勿忘字符数组长度要比实际字符时加一
⇨使用gets或scanf输入字符串时会自动末尾添加\0,其他输入字符串方式时需要手动加上\0
,否则字符串末尾会因无法识别字符串末尾而输出乱码们
(int型数组的末尾无需加\0,只有char型数组需要
(\0不是空格,他们的ASCII码不同
2.5.6string.h头文件
- strlen(字符数组)
返回字符数组第一个\0前的字符个数 - strcmp(字符数组1,字符数组2)
字符数组1>字符数组2,返回正整数
字符数组1=字符数组2,返回0
字符数组1<字符数组2,返回负整数
- 比较原则:字典序(前<后,如a<b)
- strcpy(字符数组1, 字符数组2)
把字符数组2复制给字符数组1 - strcat(字符数组1,字符数组2)
把字符数组2连接到字符数组1后
2.5.7sscanf和sprintf
string+scanf
sscanf()把字符数组screen中的内容以"%d"的格式写到n中
#include <stdio.h>
int main(){
int n;
char str[6] = "123";
sscanf(str, "%d", &n);
printf( "%d", n); //123
return 0;
}
ssprintf()把n以“%d”的格式写到str字符数组中
#include <stdio.h>
int main(){
int n = 123;
char str[6];
sprintf(str, "%d", n);
printf("%s", str); //123
return 0;
}
2.6函数
2.6.1函数的定义
局部变量
#include <stdio.h>
int main(){
int n = 123;
{
int m = 1;
printf("%d", n);//123
}
//printf("%d", m);//报错
return 0;
}
2.6.2main()函数
main()函数返回0:告知系统程序正常终止
2.6.3以数组作为函数参数
数组作为参数时,
- 第一维无需填写长度(后续需要填写长度)
- 在函数中对数组元素的修改等同于是对原数组元素的修改(普通的局部变量只是修改副本)
2.6.4函数的嵌套使用
指在一个函数中调用另一个函数
* 结构体内不能定义自己本身的数据类型,可定义自身类型的指针变量
2.7指针
2.7.1指针是什么
c中,指针表示内存地址(或者说指针指向了内存地址)
- %p
#include <stdio.h>
void f(int *p);
int main(){
int i = 1;
printf("%p", &i);//000000000062FE1C(&取地址
f(&i);
return 0;
}
void f(int *p){
printf("\n%p", p);//000000000062FE1C(指针指向内存地址
}
2.7.2指针变量
指针变量:用来存放指针
数据类型 *变量名;//*的位置在数据类型之后或变量名之前都可以
- 星号只会结合第一个变量名
⇨同时定义同种类型的指针变量时要给每个变量名之前都加上*
int *p1, p2;//p1是int*型, p2是int型
int *p3, *p4, *p5;//p3,p4,p5都是int*型
- p存放的是地址,*p是该地址中存放的元素
#include <stdio.h>
int main(){
int a = 0, *p = &a;
*p = 1;//改变该地址上所存放的内容
printf("%d", a);//1
return 0;
}
- 指针变量的加减法是两个地址间偏移的距离
例如int *p,当p+n时指int型变量的下n个int型变量地址(中间跨越了n个int型数据(即4字节)) - 对于指针变量来说,把其存储的地址类型称为基类型
基类型必须和指针变量存储的地址类型相同
2.7.3指针和数组
在C语言中,数组名称 也作为 数组的首地址 使用。
➥a + i和&a[i]等价(int a[10]
- 两个int型的指针相减,等价于求两个指针之间相差了几个int,其他数据类型同理
2.7.4使用指针变量作为函数参数
当指针类型作为函数参数的类型时,即把变量的地址传入函数。在函数中对该变量改变,改变的时该地址处的变量内容,则原先的数据也会改变。
c语言中函数的三种传值(调用)方式?
传值,传指针,传引用
1、传值调用,就是把一个变量的值传递给函数的形式参数;
对实参进行拷贝,形参的变换不会对实参有任何影响。创建和销毁对象浪费一定的时间和空间
2、引用调用和传地址调用,就是通过指针来实现的,能改变函数外的变量值,即实参会相应改变
2.7引用
引用是产生变量的别名(常量不可使用引用
#include <stdio.h>
void change(int &x){//不管是否使用引用,函数的参数名和实际传入的参数名可以不同
x = 1;//引用的&和取地址运算符&不等同,引用不是取地址
}
int main(){
int a = 0;
change(a);
printf("%d", a);//1
return 0;
}
2.8结构体
把若干个不同的数据类型的变量或数组封装在一起,以存储自定义的数据结构,方便存储一些复合结构
2.8.1结构体的定义
结构体里除了自己本身(会引起循环定义)之外的任何数据类型。
不能定义自身,但可以定义自身类型的指针变量
struct 结构体名{
//一些基本的数据结构或自定义的数据类型
}结构体变量1,结构体变量数组2,结构体指针变量3;
struct studentInfo{
int id;
char gender;//F or M
char name[20];
}stu, postgraduate[100], *p;
2.8.2访问结构体内的元素
struct studentInfo{
int id;
char name[20];
studentInfo *next;
}stu, *p;
stu.id
stu.name
stu.next
(*p).id
(*p).name
(*p).next
p->id
p->name
p->next
2.8.3结构体的初始化方式
构造函数:用来初始化结构体的一种函数
- 一般,对于结构体,内部会生成一个默认的构造函数
- 若自己重新定义了构造函数,则不能不经初始化就定义结构体变量
struct studentInfo{
int id;
char gender;
//用以不初始化就定义结构体变量
studentInfo(){}
//只初始化gender
studentInfo(char _gender){
gender = _gender;
}
//同时初始化id和gender
studentInfo(int _id, char _gender){
id = _id;
gender = _gender;
}
};
三、入门
3.5进制转换
P进制数x→十进制数y
int y = 0, product = 1;//y十进制数,product记录权重
while(x != 0){
y += (x % 10) * product;//x%10获取x的个位数
x /= 10;//去掉x的个位
product *= p;//下一权重
}
十进制y→Q进制z
除基取余法
int z[40], num = 0;//数组z存放Q进制数y的每一位,num为位数
do{
//z数组从高位z[num-1]到低位z[0]即为Q进制z,进制转换完成
z[num++] = y % Q;//除基取余
y /= Q;
}while(y != 0);//当商非零时循环
用do……while的原因:当0的时候确保是,即处理y为0的情况
四、算法初步
4.1排序
4.1.1选择排序
- 思想:每次选择第i(1~n)小的元素并放到A[i]处。A[i]和当前有序区间[1, i-1]形成新的有序区间[1,i]。在n趟操作后,所有元素有序。
- 简单选择(O(n^2))),堆排序(O(nlogn))
void selectSort(){//简单选择,以从小到大为例
for(int i = 1; i <= n; i++){
int k = i;
for(int j = i; j <= n; j++){
if(A[j] > A[k]) k = j;//记录最小元素下标
}
int temp = A[i];//把选出来的最i小元素放到A[i]
A[i] = A[j];
A[j] = temp;
}
}
4.1.2插入排序
- 思想:对于序列A的n个元素(1~n),i从2到n枚举,进行n-1趟排序。假设某一趟时,序列A的前i-1个元素A[1]-A[i-1]已经有序,从[1, i-1]中寻找某个位置j,使得A[j]之前的元素前移一位,再把保存的A[i]放到A[j]处。
- 直接插入排序,折半插入排序(O(n^2)),希尔排序(组间进行直接插入排序,依次缩小组距)。
int A[maxn],n;//n为元素个数,数组下标为1~n
void insertSort(){
for(int i = 2; i <= n; i++){//首元素视为一个已经有序的序列
int t = A[i], j = i;
while(j > 1 && t < A[j-1]){
A[j-1] = A[j];
j--;
}
A[j] = temp;//插入到位置j
}
}
4.1.3排序题和sort函数的应用
cmp函数的编写
注意strcmp的返回值未必是-1或+1(具体与编译器有关),则return strcmp(a.name, b.name) == -1的写法是错误的。
排名的实现
排名规则通常是:分数不同的排名不同,分数相同的排名相同但占用一个排位。如四个考研人的分数是320, 350, 350, 380,则这四名学生的排名是1,2,2,4
- 直接输出排名
int r = 1;
for(int i = 0; i < n; i++){
if(i > 0 && stu[i].score != stu[i].score) r = i + 1;
//输出当前个体信息或者令stu[i].r = r以记录排名
}
- 统计排名
stu[i].r = 1;
for(int i = 0; i < n; i++){
if(stu[i].score == stu[i-1].score) stu[i].r = stu[i - 1].r;
else stu[i] = i + 1;
}
4.2散列
4.2.1散列的定义和整数散列
- 散列
属于查找,它不以关键字的比较为基本操作,采用直接寻址技术。在理想情况下,查找的期望时间为O(1)。 - hash函数
就是把任意长的输入字符串变化成固定长的输出字符串的一种函数。输出字符串的长度称为hash函数的位数。
散列(Hashing)通过散列函数将要检索的项与索引(散列,散列值)关联起来,生成一种便于搜索的数据结构(散列表)。 - 冲突
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称之为冲突。
这些发生碰撞的不同关键字称为同义词。
哈希冲突是不可避免的,因为键的数目总是比索引的数目多,不管是多么高明的算法都不可能解决这个问题。就算键的数目比索引的数目少,必有一个输出串对应多个输入串,冲突还是会发生。 - 哈希函数的构造方法
(1)直接定址法:
取关键字或关键字的某个线性函数值为哈希地址:H(key) = key 或 H(key) = a·key + b
其中a和b为常数,这种哈希函数叫做自身函数。
注意:由于直接定址所得地址集合和关键字集合的大小相同。因此,对于不同的关键字不会发生冲突。但实际中能使用这种哈希函数的情况很少。
(2)相乘取整法:
首先用关键字key乘上某个常数A(0 < A < 1),并抽取出key.A的小数部分;然后用m乘以该小数后取整。
注意:该方法最大的优点是m的选取比除余法要求更低。比如,完全可选择它是2的整数次幂。虽然该方法对任何A的值都适用,但对某些值效果会更好。Knuth建议选取 0.61803……。
(3)平方取中法:
取关键字平方后的中间几位为哈希地址。
通过平方扩大差别,另外中间几位与乘数的每一位相关,由此产生的散列地址较为均匀。这是一种较常用的构造哈希函数的方法。
将一组关键字(0100,0110,1010,1001,0111)
平方后得(0010000,0012100,1020100,1002001,0012321)
若取表长为1000,则可取中间的三位数作为散列地址集:(100,121,201,020,123)。
(4)除留余数法:
取关键字被数p除后所得余数为哈希地址:H(key) = key MOD p (p ≤ m)。
注意:这是一种最简单,也最常用的构造哈希函数的方法。它不仅可以对关键字直接取模(MOD),也可在折迭、平方取中等运算之后取模。值得注意的是,在使用除留余数法时,对p的选择很重要。一般情况下可以选p为质数或不包含小于20的质因素的合数。
(5)随机数法:
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random (key),其中random为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。
- 哈希冲突解决方法
(1)开放定址法:
就是在发生冲突后,通过某种探测技术,去依次探查其他单元,直到探查到不冲突为止,将元素添加进去。
假如是在index的位置发生哈希冲突,那么通常有一下几种探测方式:
①线性探测法(线性探测再散列)
向后依次探测index+1,index+2…位置,看是否冲突,直到不冲突为止,将元素添加进去。
②平方探测法
不探测index的后一个位置,而是探测2i位置,比如探测20位置上时发生冲突,接着探测2^1位置,依此类推,直至冲突解决。
注意:
(1)用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)置空。
(2)链地址法(开散列法)
基本思想:
链表法就是在发生冲突的地址处,挂一个单向链表,然后所有在该位置冲突的数据,都插入这个链表中。插入数据的方式有多种,可以从链表的尾部向头部依次插入数据,也可以从头部向尾部依次插入数据,也可以依据某种规则在链表的中间插入数据,总之保证链表中的数据的有序性。Java的HashMap类就是采取链表法的处理方案。
例:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按哈希函数 H(key) = key MOD13 和链地址法处理冲突构造所得的哈希表为:
(3)再哈希法:(双散列法)
在发生哈希冲突后,使用另外一个哈希算法产生一个新的地址,直到不发生冲突为止。这个应该很好理解。i
再哈希法可以有效的避免堆积现象,但是缺点是不能增加了计算时间和哈希算法的数量,而且不能保证在哈希表未满的情况下,总能找到不冲突的地址。
4.3递归
4.4贪心
4.4.1简单贪心
贪心是求解一类最优化问题的方法,它总是考虑在当前状态下局部最优(或较优)的策略,来使全局的结果达到最优。一般而言,若想到某个似乎可行的策略,且自己无法举出反例,就先实现看看啦。
4.4.2区间贪心
下面看看区间不相交问题:给出N个开区间(x,y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。
首先区间可以视为线段。
以先选择右端点最小的线段为例,即结束越早的线段,越应该去优先选择。
==> 把所有线段按照右端点升序排序,进行枚举判断, 上一条的右端点<=当前线段的左端点,即没有相交,可以选择
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6 + 10;
struct line{
int l,r;
bool operator < (line &a){//重载小于运算符,按照右端点升序
return r < a.r;
}
}l[maxn];
int main(){
int n, last = 0, ans = 0;
cin >> n;
for(int i = 0; i < n; i++){
cin >> l[i].l >> l[i].r;
}
sort(l, l + n);
for(int i = 0; i < n; i++){
if(l[i].l >= last){
ans++;
last = l[i].r;
}
}
printf("%d", ans);
return 0;
}
若先选择左端点最大的线段,则排序按照左端点从大到小
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6 + 10;
struct line{
int l,r;
}l[maxn];
bool cmp(line l1, line l2){
if(l1.l != l2.l) return l1.l > l2.l;
return l1.r < l2.r;
}
int main(){
int n, last, ans = 1;
cin >> n;
for(int i = 0; i < n; i++){
cin >> l[i].l >> l[i].r;
}
sort(l, l + n, cmp);
last = l[0].l;
for(int i = 0; i < n; i++){
if(l[i].r <= last){
ans++;
last = l[i].l;
}
}
printf("%d", ans);
return 0;
}
与该问题类似的有 区间选点问题:给出N个闭区间[x, y],求最少需要确定多少个点,才能使每个闭区间中至少存在一个点。例如对闭区间[1,4],[2,6],[5,7],需要两个点(例如3,5)才能保证每个闭区间内都有至少有一个点。
其实仔细一想会发现,这个问题和区间不相交问题的策略是一致的。以左端点最大的线段而言,取左端点即可。则只需要把区间不相交问题的代码判断条件l[i]. r <= last
改为l[i].r < last
(闭区间,上一个区间的左端点可以覆盖相交的两个区间上)。
总之,贪心是用来解决一类最优化问题,并希望由局部最优策略来推得全局最优的思想。贪心适用的问题一定满足最优子结构性质,即一个问题的最优解可以由它的子问题的最优解有效构造出来。
4.5二分
4.5.1二分查找
当二分上界超过int型数据范围的一半,则当要查询元素在序列较靠后位置时,mid=(left+right)/2中的left+right有可能超出int而导致溢出,则使用以下等价语句代替避免溢出
mid = left + (right - left) / 2;
4.5.3快速幂
给定三个正整数a,b,m(a < 109,b < 1018,1 < m < 109),求ab%m
数据量很大,直接循环(时间复杂度O(b))容易超时
基于二分的思想,也称为二分幂
- b是奇数, 则ab=a*ab-1
- b是偶数, 则ab=ab/2*ab/2
long long binaryPow(long long a, long long b, long long m){
if(b == 0) return 1;
if(b%2 == 1) return a * binaryPow(a, b - 1, m) % m;
else return binaryPow(a, b / 2, m)*binaryPow(a, b / 2, m)%m;
}
4.6two pointers
4.6.1何为two pointers
充分利用序列递增的性质。
最原始的含义是针对例1而言的,广义上的two pointers是利用问题本身和序列的特性,使用两下标i、j对序列进行扫描(可以同向扫描,也可反向扫描),以较低的复杂度(一般是O(n)的复杂度)解决问题。
例1,给定一个递增的正整数序列和一个正整数m,求序列中的两个不同位置的数a和b,满足a+b=m,输出所有组合。例如给定序列为{1,2,3,4,5,6}和正整数8,则输出为
2 6
3 5
直接暴力两层for循环的话,时复O(n2)。
注意到所给序列是递增的,分析规律
- a[i]+a[j]==m,满足条件输出该组合,i++,j–
- a[i]+a[j]>m,j–
- a[i]+a[j]<m,i++
while(i < j){
if(a[i] + a[j] == m){
printf("%d %d\n", a[i], a[j]);
i++;
j--;
}
else if(a[i] + a[j] > m) i++;
else j--;
}
例2,把两个递增序列合并为一个递增序列
int merge(int A[], int B[], int C[], int m, int n){
int i = 0, j = 0, k = 0;
while(i < m && j < n){
if(A[i] < B[j]) C[k++] = A[i++];
else C[k++] = B[j++];
}
while(i < m) C[k++] = A[i++];
while(j < n) C[k++] = B[j++];
return k;
}
4.6.2归并排序
4.6.3快排
4.7其他
4.7.1打表
空间换时间,一般指把所有可能需要用到的结果事先计算出来,后续需要时可以直接查表。
- 常见用法有:
- 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果
- 在程序B分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果
- 对一些感觉不会做的题,先用暴力计算小范围数据的结果,再找规律,或许能打开思路