算法笔记之蓝桥杯&pat系统备考(1)

86 篇文章 5 订阅

题代码

算法笔记之蓝桥杯&pat系统备考(2)

一、刷题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~948~57
A~Z65~90
a~z97~122

notice:小写字母比大写字母大32

  • 引用:起个别的昵称
    • &的两种用法
    1. 引用:别名
    2. 取地址运算符
    • 注意:常量不可使用引用

2.1.4符号常量和const常量

常量:一旦确定其值后不能改变

  • 宏定义(或宏替换):直接把对应的部分替换,然后再进行编译和运行

因此使用时把能加的地方都加上括号,防止改变运算顺序

#define 标识符 常量
  • const常量(!定义常量时推荐使用
const 数据类型 变量名 = 常量;

2.2.2scanf和printf

在这里插入图片描述
字符串或者数组不需要加取地址符&

  • scanf的%c格式可以读入空格和换行,即空格和换行可以作为字符的内容
  • 除%c外,scanf对其他格式符(例如%d,%s),以空格和换行作为读入结束的标志
    在这里插入图片描述
  1. double型变量,输入是%lf,输出是%f(开始是殷勤,之后就顺便了~
  2. 常用的输出格式
  • %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. 字符数组的初始化
    字符输在可通过字节赋值字符串来初始化(其他位置不可以这样赋值整个字符串
  2. 字符数组的输入输出
    (1)scanf&printf
    %c 可识别空格和换行作为内容
    %s 用空格或换行作为结束标志
    (2)getchar&putchar
    对象是单个字符
    (3)gets&puts
    gets识别换行作为输入结束(同scanf %s
    所以如果输入其他在用gets时,要先用getchar吸收换行符
  3. 字符数组的存放方式
    空字符\0:表示存放的字符串的结尾。占用一个字符位
    ⇨开字符数组时勿忘字符数组长度要比实际字符时加一
    ⇨使用gets或scanf输入字符串时会自动末尾添加\0,其他输入字符串方式时需要手动加上\0
    ,否则字符串末尾会因无法识别字符串末尾而输出乱码们
    (int型数组的末尾无需加\0,只有char型数组需要
    (\0不是空格,他们的ASCII码不同

2.5.6string.h头文件

  1. strlen(字符数组)
    返回字符数组第一个\0前的字符个数
  2. strcmp(字符数组1,字符数组2)
    字符数组1>字符数组2,返回正整数
    字符数组1=字符数组2,返回0
    字符数组1<字符数组2,返回负整数
  • 比较原则:字典序(前<后,如a<b)
  1. strcpy(字符数组1, 字符数组2)
    把字符数组2复制给字符数组1
  2. 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,结构体指针变量3struct 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))容易超时

基于二分的思想,也称为二分幂

  1. b是奇数, 则ab=a*ab-1
  2. 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)。
注意到所给序列是递增的,分析规律

  1. a[i]+a[j]==m,满足条件输出该组合,i++,j–
  2. a[i]+a[j]>m,j–
  3. 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中就可以直接使用这些结果
    • 对一些感觉不会做的题,先用暴力计算小范围数据的结果,再找规律,或许能打开思路

4.7.2活用递推

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值