01、介绍
本题单适合零基础同学快速入门提升,知识点涵盖语法到常考常用的简单数据结构,配合知识点图文讲解,完成本题单,你可以解决笔试中至少30%的题目。
02、代码的运行
Q1、Hello Nowcoder
1、题目描述
描述
为了测试您的开发环境配置,请编写一个程序,运行后输出一条问候信息,表达对牛客社区的欢迎。
具体而言,你需要输出的问候信息为 “Hello Nowcoder!”(注意,不含双引号)。
输入描述:
本题没有任何输入。
输出描述:
输出一行一个字符串,内容为 Hello Nowcoder! 。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
printf("Hello Nowcoder!");
}
03、基本数据类型
Q2、牛牛学说话之-整数
1、题目描述
描述
牛牛刚刚出生,嗷嗷待哺,一开始他只能学说简单的数字,你跟他说一个整数,他立刻就能学会。
输入一个整数 n,输出这个整数。
输入描述:
输入一行一个整数 n(−104 ≤ n ≤ 104)。
输出描述:
输出一行一个整数,即输入的整数 nn。
示例1
输入:
3
输出:
3
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n = 0;
scanf("%d", &n);
printf("%d", n);
}
Q3、牛牛学说话之-浮点数
1、题目描述
描述
会说整数之后,牛牛开始尝试浮点数(小数)
输入一个浮点数 x ,输出该浮点数。
输入描述:
输入一个浮点数 x 。
输出描述:
输出浮点数 x 。注意,由于浮点数存在误差,只要您的答案与标准答案之间的误差不超过 10−3,您的答案就会被认为是正确的。
示例1
输入:
1.359578
输出:
1.360
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
double d = 0;
scanf("%lf", &d);
printf("%0.3lf", d);
}
Q4、牛牛学说话之-字符串
1、题目描述
描述
会说浮点数(小数)之后,牛牛开始尝试字符串。
输入一个仅由大写字母、小写字母和数字构成的字符串 s,长度为 n,其中 1 ≦ n ≦ 104 。请输出该字符串。
输入描述:
输入一行一个长度为 n,且只由大写字母、小写字母和数字构成的字符串 s(1 ≦ n ≦ 104)。
输出描述:
输出字符串 s。
示例1
输入:
nowcoder
输出:
nowcoder
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
string s;
cin >> s;
cout << s;
}
Q5、复读机
1、题目描述
描述
无论输入了什么,您都应输出所输入的内容,输出之间以换行隔开。
特别的,对于浮点数,由于浮点数精度问题,你需要保留一位小数后输出。
具体的输入内容,请参考输入格式。
输入描述:
输入包含 5 行:
第一行为一个整数 a(−109 ≤ a ≤ 109)。
第二行为一个整数 b(−1018 ≤ b ≤ 1018)。
第三行为一个浮点数 c(−109 ≤ a ≤ 109)。
第四行为一个小写字母 d。
第五行为一个字符串 e(e 中不含空格或换行符)。
输出描述:
输出 5 行,且每行都与对应行输入内容完全相同。特别的,对于浮点数 c,由于浮点数精度问题,你需要 保留一位小数 后输出。
示例1
输入:
114 1145141919810 3.1415926 k WangZai666!
输出:
114 1145141919810 3.1 k WangZai666!
2、代码实现
C++
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
int a;
long b;
double c;
char d;
char e[20];
scanf("%d\n", &a);
scanf("%ld\n", &b);
scanf("%lf\n", &c);
scanf("%c\n", &d);
scanf("%s\n", e);
printf("%d\n", a);
printf("%ld\n", b);
printf("%.1f\n", c);
printf("%c\n", d);
printf("%s\n", e);
return 0;
}
04、运算符与优先级
Q6、牛牛学加法
1、题目描述
描述
给定两个整数 a 和 b,其中 0 ≦ a,b ≦ 1000,请计算它们的和并输出结果。
输入描述:
在一行中输入两个整数 a, b(0 ≦ a,b ≦ 1000)。
输出描述:
输出一个整数,表示 a+b 的值。
示例1
输入:
1 2
输出:
3
说明:
1+2=3,输出结果为 3。
示例2
输入:
1000 0
输出:
1000
说明:
1000+0=1000,输出结果为 1000。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
cout << a + b << endl;
}
}
Q7、疫情死亡率
1、题目描述
描述
某种病毒正在人群中肆虐,你作为龙国最强病毒专家的最强助手,需要帮助他分析目前病毒的死亡率。
给定感染某种病毒的确诊人数 c 与死亡人数 d ,请计算该种病毒的死亡率,死亡率定义为 d/c × 100%。
输入描述:
输入一行两个整数 c,d (1 ≦ c,d ≦ 109),分别表示某种病毒造成的确诊人数和死亡人数。
输出描述:
输出死亡率,以百分数形式表示,结果保留小数点后三位,并在末尾加上百分号 %。
示例1
输入:
10433 280
输出:
2.684%
2、代码实现
C++
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
double a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
double d = b/a*100;
printf("%.3lf", d);
cout << "%" << endl;
}
}
Q8、计算带余除法
1、题目描述
描述
给定两个整数 a 和 b(1 ≦ a,b ≦ 104),请计算 a 除以 b 的整数商和余数。
输入描述:
输入一行两个整数 a,b(1 ≦ a,b ≦ 104),分别表示被除数和除数。
输出描述:
输出两个整数,分别表示 a 除以 b 的商和余数,中间用空格隔开。
示例1
输入:
15 2
输出:
7 1
说明:
15÷2=7 余 1,因此输出 7 和 1。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
cout << a/b << " " << a%b << endl;
}
}
Q9、整数的个位
1、题目描述
描述
给定一个整数 a(−109 ≦ a ≦ 109),求该整数的个位数字,定义为该整数绝对值对 1010 取余的结果。
输入描述:
在一行中输入一个整数 a,满足 (−109 ≦ a ≦ 109)。
输出描述:
输出一个整数,表示 a 的个位数字,即 ∣a∣ mod 10。
示例1
输入:
114
输出:
4
说明:
∣114∣=114,114 mod 10=4,因此输出 4。
示例2
输入:
-514
输出:
4
说明:
∣−514∣=514,514 mod 10=4,因此输出 4。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a;
scanf("%d", &a);
printf("%d", a%10);
}
Q10、整数的十位
1、题目描述
描述
给定一个整数 a(−109 ≦ a ≦ 109),请计算该整数的十位数字,定义为 ⌊∣a∣/10⌋ mod 10。
输入描述:
在一行中输入一个整数 a,满足 (−109 ≦ a ≦ 109)。
输出描述:
输出一个整数,表示 aa 的十位数字,即 ⌊∣a∣/10⌋ mod 10。
示例1
输入:
114
输出:
1
说明:
∣114∣=114∣114∣=114,⌊114/10⌋=11,11 mod 10=1,因此输出 1。
示例2
输入:
-514
输出:
1
说明:
∣−514∣=514,⌊514/10⌋=51,51 mod 10=1,因此输出 1。
示例3
输入:
6
输出:
0
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a;
scanf("%d", &a);
printf("%d", a/10%10);
}
Q11、平方根
1、题目描述
描述
给定一个正整数 n,求 n 的整数部分,即对 n 向下取整的结果。
例如,sqrt(5)=2.236… 向下取整后为 2;sqrt(16)=4.000… 向下取整后为 4。
输入描述:
在一行中输入一个整数 n (1 ≦ n ≦ 109)。
输出描述:
输出一个整数,表示 sqrt(n) 向下取整后的值。
示例1
输入:
5
输出:
2
说明:
5≈2.236,向下取整后为 2。
示例2
输入:
16
输出:
4
说明:
16=4.000,向下取整后为 4。
2、代码实现
C++
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main() {
int a;
scanf("%d", &a);
double ret = sqrt(a);
printf("%d", (int)ret);
return 0;
}
05、顺序结构
Q12、反向输出一个四位数
1、题目描述
描述
给定一个四位整数 n(1000 ≦ n ≦ 9999),将其各位数字反向输出。若反向后高位为零,也需保留该零。
输入描述:
在一行中输入一个四位整数 n(1000 ≦ n ≦ 9999)。
输出描述:
输出一个四位整数,为 n 反向后的结果;若高位为零,也需保留。
示例1
输入:
1234
输出:
4321
说明:
在这个样例中,输入的四位数是 1234,反向输出后得到 4321。
示例2
输入:
1000
输出:
0001
说明:
在这个样例中,输入的四位数是 1000,反向输出后得到 0001,注意保留了前导零。
2、代码实现
C++
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
int a;
scanf("%d", &a);
printf("%d%d%d%d", a%10, a/10%10, a/100%10, a/1000%10);
}
Q13、温标转换
1、题目描述
描述
作为远近闻名的大聪明,旺仔哥哥非常熟悉开尔文温度、摄氏温度和华氏温度之间的转换。令符号 K 表示开尔文温度,C 表示摄氏温度,F 表示华氏温度,它们的转换公式如下:
C=K−273.15
F=C×1.8+32
旺仔哥哥想计算某一开尔文温度对应的华氏温度,请你完成该任务。
输入描述:
在一行中输入一个实数 K,满足 0 ≦ K ≦ 105,表示开尔文温度。
输出描述:
输出一个实数 F,表示 K 对应的华氏温度。注意,由于浮点数存在误差,只要您的答案与标准答案之间的误差不超过 10−3,您的答案就会被认为是正确的。
示例1
输入:
173.56
输出:
-147.261999999
说明:
C=173.56−273.15=−99.59,F=−99.59×1.8+32≈−147.262,因此输出 −147.262000 也可满足精度需求。
示例2
输入:
273.15
输出:
32.00000000000
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
double k;
scanf("%lf", &k);
printf("%lf", (k-273.15)*1.8+32);
}
Q14、绕距
1、题目描述
2、代码实现
C++
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
double m, o;
m = abs(x1 - x2) + abs(y1 - y2);
o = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
double ret = m - o;
printf("%lf", ret);
}
Q15、求四位数各个数位之和
1、题目描述
描述
给定一个四位整数 n(1000 ≦ n ≦ 9999),请计算该整数各个数位之和。
输入描述:
在一行中输入一个四位整数 n(1000 ≦ n ≦ 9999)。
输出描述:
输出一个整数,表示 n 的各个位数之和。
示例1
输入:
1270
输出:
10
说明:
0+7+2+1=10。
示例2
输入:
9999
输出:
36
说明:
9999 的各位数字之和 9+9+9+9=36。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n, sum = 0;
scanf("%d", &n);
while (n) {
sum += n % 10;
n /= 10;
}
printf("%d", sum);
}
Q16、时间转换
1、题目描述
描述
给定秒数 seconds,将其转换为对应的小时数、分钟数和秒数,使得总时间不变,但分钟数和秒数都不超过 59。
输入描述:
在一行中输入一个整数 seconds,表示要转换的秒数,满足 (0 ≦ seconds ≦ 108)。
输出描述:
一行,包含三个整数,依次为输入整数对应的小时数、分钟数和秒数(可能为零),中间用一个空格隔开。
示例1
输入:
3661
输出:
1 1 1
说明:
将 3661 秒转换:3661÷3600=1 小时,余 61 秒;61÷60=1 分钟,余 1 秒,结果为 1 1 1。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int second, min, hour;
scanf("%d", &second);
hour = second / 3600;
second %= 3600;
min = second / 60;
second %= 60;
printf("%d %d %d", hour, min, second);
}
Q17、计算机内存
1、题目描述
描述
我们可以看到题目描述的上方有一个空间限制 256 MB。在计算机中,一个整数占据 4 字节的内存;
- 1 MB=1024 KB;
- 1 KB=1024 B;
- 1 B=1 字节。
那么,给定 n MB 的内存,请问可以存储多少个整数?
输入描述:
在一行中输入一个整数 n(1 ≦ n ≦ 256),表示内存大小(单位 MB)。
输出描述:
输出一个整数,表示在 n MB 内存中最多可以存储的整数个数。
示例1
输入:
1
输出:
262144
说明:1 MB=1024×1024 B=220 B,每个整数占用 4 字节,因此可存储 220/4=262144 个整数。
示例2
输入:
256
输出:
67108864
说明:256 MB=256×220 B,每个整数占用 4 字节,因此可存储 256×220/4=256×218=67108864 个整数。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
scanf("%d", &n);
printf("%d", n * 262144);
}
Q18、牛牛学立体
1、题目描述
描述
给定一个长方体的长 a、宽 b 和高 c(1 ≦ a,b,c ≦1000),请计算该长方体的表面积和体积。
输入描述:
在一行中输入三个整数 a,b,c,分别表示长、宽和高,满足 1 ≦ a,b,c ≦1000。
输出描述:
输出两行:第一行输出一个整数,表示表面积 S,第二行输出一个整数,表示体积 V。
示例1
输入:
1 1 1
输出:
6 1
说明:
当 a=b=c=1 时,表面积 S=2(1⋅1+1⋅1+1⋅1)=6;体积 V=1⋅1⋅1=1。
备注:
对于不熟悉几何表面积、体积求法的同学,可以参考下面的公式: ∙ S=2(ab+bc+ca) ∙ V=abc
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
int s = 2 * (a * b + b * c + a * c);
int v = a * b * c;
printf("%d\n%d", s, v);
}
Q19、成绩
1、题目描述
描述
牛牛最近学习了 C++ 入门课程,这门课程的总成绩按照如下权重计算:
- 作业成绩 A 占 20%;
- 小测成绩 B 占 30%;
- 期末考试成绩 C 占 50%。
因此,总成绩 S 计算公式为:
S=A×20%+B×30%+C×50%
输入描述:
在一行中输入三个非负整数 A,B,C,分别表示牛牛的作业成绩、小测成绩和期末考试成绩,满足 (0 ≦ A,B,C ≦ 100) 且 A,B,C 均为 10 的整数倍。
输出描述:
输出一个整数 S,表示牛牛的总成绩,满分为 100。
示例1
输入:
100 100 100
输出:
100
说明:
S=100×20%+100×30%+100×50%=20+30+50=100。
示例2
输入:
70 80 90
输出:
83
说明:
S=70×20%+80×30%+90×50%=14+24+45=83。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b, c;
while (cin >> a >> b >> c) { // 注意 while 处理多个 case
cout << a * 0.2 + b * 0.3 + c * 0.5 << endl;
}
}
Q20、小乐乐求和
1、题目描述
描述
小乐乐最近接触了求和符号 Σ,他想计算从 1 到 n 的自然数之和,但是小乐乐很笨,请你帮助他解答。
输入描述:
在一行中输入一个正整数 n(1 ≦ n ≦ 109)。
输出描述:
输出一个整数,表示从 1 到 n 的自然数之和。
示例1
输入:
1
输出:
1
说明:
当 n=1 时,1=1。
示例2
输入:
10
输出:
55
说明:
当 n=10 时,1+2+⋯+10=55。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
long long n;
cin >> n;
cout << (1 + n)*n / 2;
}
06、选择结构
Q21、明天星期几?
1、题目描述
描述
我们以整数 1∼7 分别表示星期一到星期天。已知今天是星期 d,请你推算明天是星期几。
输入描述:
在一行中输入一个整数 d(1 ≦ d ≦ 7),表示今天是星期 d。
输出描述:
输出一个整数,表示明天是星期几(范围同样为 1∼7)。
示例1
输入:
1
输出:
2
说明:
今天为星期一(1),明天为星期二(2)。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a;
cin >> a;
cout << a % 7 + 1;
}
Q22、判断闰年
1、题目描述
描述
给定一个整数 n,判断其是否为闰年。闰年的判定规则如下:
- 如果 n 能被 400 整除,则为闰年;
- 否则如果 n 能被 4 整除且不能被 100 整除,则为闰年;
- 否则,不是闰年。
输入描述:
在一行中输入一个整数 n,满足 (1 ≦ n ≦ 2018)。
输出描述:
输出一个字符串,若 n 为闰年,输出 “yes”(不含双引号);否则输出 “no”(不含双引号)。
示例1
输入:
2000
输出:
yes
说明:
2000 能被 400 整除,因此是闰年。
示例2
输入:
1900
输出:
no
说明:
1900 能被 100 整除但不能被 400 整除,因此不是闰年。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int year;
cin >> year;
if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
cout << "yes";
} else {
cout << "no";
}
}
Q23、比大小
1、题目描述
描述
比较整数 a 和 b 的大小。若 a<b,输出 “<”;若 a=b,输出 “=”;若 a>b,输出 “>”。
输入描述:
在一行中输入两个整数 a,b(1 ≦ a,b ≦ 10000),用空格隔开。
输出描述:
输出一个字符,表示比较结果,不包含引号。
示例1
输入:
1 2
输出:
<
说明:
因为 1<2,所以输出 “<”。
示例2
输入:
1 1
输出:
=
示例3
输入:
2 1
输出:
>
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
if (a < b) {
cout << "<";
} else if (a == b) {
cout << "=";
} else {
cout << ">";
}
}
Q24、卡拉兹函数
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n % 2) {
cout << 3 * n + 1;
} else {
cout << n / 2;
}
}
Q25、牛妹数
1、题目描述
描述
如果一个整数是偶数且大于 50,我们称其为牛妹数。给定一个整数 n,判断其是否为牛妹数。
输入描述:
在一行中输入一个整数 n,满足 1 ≦ n ≦ 100。
输出描述:
若 n 是牛妹数,输出 “yes”(不含双引号);否则输出 “no”(不含双引号)。
示例1
输入:
50
输出:
no
说明:
50 为偶数但不大于 50,因此不是牛妹数,输出 no。
示例2
输入:
52
输出:
yes
说明:
52 为偶数且大于 50,因此是牛妹数,输出 yes。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n > 50 && n % 2 == 0) {
cout << "yes";
} else {
cout << "no";
}
}
Q26、牛牛是否被叫家长
1、题目描述
描述
牛牛的班级进行了一次期中考试,考试一共有 3 门科目:数学、语文和英语。班主任决定给没有通过考核的同学家长开一场酣畅淋漓的家长会,考核标准为三科平均分不低于 60 分。三科平均分计算公式为:
Avg=(A+B+C)/3
如果 Avg < 60 ,则牛牛会被请家长;否则不会被请家长。
输入描述:
在一行中输入三个整数 A,B,C,分别表示牛牛的数学、语文和英语成绩,用空格隔开,满足 0 ≦ A,B,C ≦100。
输出描述:
如果牛牛会被请家长,输出 “YES”(不含双引号);否则输出 “NO”(不含双引号)。
示例1
输入:
80 60 50
输出:
NO
说明:
平均分为 (80+60+50)/3≈63.33≧60,牛牛及格,不会被请家长,输出 NO。
示例2
输入:
70 55 40
输出:
YES
说明:
平均分为 (50+50+50)/3=50<60,牛牛未通过考核,会被请家长,输出 YES。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
if (a + b + c < 180) {
cout << "YES";
} else {
cout << "NO";
}
}
Q27、最大最小值
1、题目描述
描述
给定三个整数 a,b,c(1 ≦ a,b,c ≦ 106),请输出它们中的最大值和最小值。
输入描述:
在一行中输入三个整数 a,b,c(1 ≦ a,b,c ≦ 106),用空格隔开。
输出描述:
输出两行:
第一行输出 The maximum number is : X
,其中 X 为最大值;
第二行输出 The minimum number is : Y
,其中 Y 为最小值。
示例1
输入:
1 2 3
输出:
The maximum number is : 3 The minimum number is : 1
说明:
输入为 1,2,3,最大值为 3,最小值为 1。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b, c;
int minnum, maxnum;
cin >> a >> b >> c;
minnum = min(a, min(b, c));
maxnum = max(a, max(b, c));
cout << "The maximum number is : " << maxnum << endl;
cout << "The minimum number is : " << minnum;
}
Q28、四季
1、题目描述
描述
气象意义上,通常以 3∼5 月为春季 (spring),6∼8 月为夏季 (summer),9∼11 月为秋季 (autumn),12 月至来年 2 月为冬季 (winter)。请根据输入的年月(格式 YYYYMM)输出对应的季节。
输入描述:
在一行中输入一个六位整数 YYYYMM,表示年份和月份,其中 YYYY 为四位年份,MM 为两位月份(01~12)。
输出描述:
输出一个字符串,为对应的季节英文名称(全部小写字母),即 “spring”、“summer”、“autumn” 或 “winter”(均不含双引号)。
示例1
输入:
201901
输出:
winter
说明:
输入 201901 表示 2019 年 1 月,1 月为冬季,输出 winter。
示例2
输入:
202506
输出:
summer
说明:
输入 202506 表示 2025 年 6 月,6 月为夏季,输出 summer。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n, month;
cin >> n;
month = n % 100;
if (month >= 3 && month <= 5) {
cout << "spring";
} else if (month >= 6 && month <= 8) {
cout << "summer";
} else if (month >= 9 && month <= 11) {
cout << "autumn";
} else {
cout << "winter";
}
}
07、循环结构
Q29、多组输入a+b II
1、题目描述
描述
给定多组数据,每组数据包含两个整数 a,b(0 ≦ a,b ≦ 100),请计算每组数据的和。
输入描述:
第一行输入一个整数 T(1 ≦ T ≦ 100),表示数据组数。
接下来 T 行,每行输入两个整数 a,b(0 ≦ a,b ≦ 100),用空格隔开。
输出描述:
对于每组测试数据,在一行中输出一个整数,表示 a+b 的结果。
示例1
输入:
2 1 1 2 2
输出:
2 4
说明:
第一组:1+1=2;第二组:2+2=4。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
int a, b;
cin >> n;
while (cin >> a >> b) { // 注意 while 处理多个 case
cout << a + b << endl;
}
}
Q30、多组数据a+b III
1、题目描述
描述
计算多组测试用例中两个整数 a,b 的和。当输入的两个整数均为 0 时,表示输入结束,不再处理该组。
输入描述:
多组测试用例,每行输入两个整数 a,b(0 ≦ a,b ≦ 1000),用空格隔开。当且仅当 a=0 且 b=0 时,输入结束,该组数据不处理。
输出描述:
对于每组测试用例 a,b,输出一个整数,表示 a+b 的结果,每个结果占一行。
示例1
输入:
1 1 2 2 0 0
输出:
2 4
说明:
第一组:1+1=2;第二组:2+2=4;遇到 0 0 后结束,不处理。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
if (a == 0 && b == 0) {
break;
}
cout << a + b << endl;
}
}
Q31、素数判断
1、题目描述
描述
给定一个正整数 n,判断其是否为素数。素数定义为大于 1 且仅能被 1 和自身整除的正整数。
输入描述:
第一行输入一个整数 T(1 ≦ T ≦ 10),表示需要判断的整数个数。
接下来 T 行,每行输入一个正整数 n(1 ≦ n ≦ 105)。
输出描述:
输出 T 行,每行对应一个测试用例:若 n 是素数,输出 “Yes”(不含双引号);否则输出 “No”(不含双引号)。
示例1
输入:
2 1 2
输出:
No Yes
说明:
第一个测试用例 n=1,1 不是素数,输出 No;第二个测试用例 n=2,2 是素数,输出 Yes。
2、代码实现
C++
#include <iostream>
using namespace std;
bool isPrime(int n) {
if (n <= 1) {
return false;
}
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
int n;
cin >> n;
while (cin >> n) {
if (isPrime(n)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
Q32、牛牛学数列
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n % 2) {
cout << n / 2 + 1;
} else {
cout << -n / 2;
}
}
Q33、牛牛学数列2
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
double ret = 0;
for (int i = 1; i <= n; ++i) {
ret += 1.0 / i;
}
printf("%.6lf", ret);
}
Q34、最大的差
1、题目描述
2、代码实现
C++
#include <climits>
#include <iostream>
using namespace std;
int main() {
int n, a, minnum = INT_MAX, maxnum = INT_MIN;
cin >> n;
while (n--) {
cin >> a;
minnum = min(minnum, a);
maxnum = max(maxnum, a);
}
cout << maxnum - minnum;
}
Q35、牛牛学数列4
1、题目描述
描述
请你帮助牛牛计算公式以下公式的结果:
- 1+(1+2)+(1+2+3)+⋯+(1+2+3+⋯+n).
输入描述:
输入一个整数 n(1 ≦ n ≦ 100) 如题意描述。
输出描述:
输出一个整数表示公式计算结果。
示例1
输入:
4
输出:
20
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n, sum = 0;
cin >> n;
for (int i = 1; i <= n; ++i) {
sum += i * (n - i + 1);
}
cout << sum;
}
Q36、牛牛学数列5
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int a = 1, b = 1, c = 1;
while (n--) {
a = b;
b = c;
c = a + b;
}
cout << a;
}
Q37、数位之和
1、题目描述
描述
给定一个整数 n(−109 ≦ n ≦ 109),请计算其所有数位之和。若 n 为负数,请先取其绝对值。
输入描述:
在一行中输入一个整数 n,满足 −109 ≦ n ≦ 109。
输出描述:
输出一个整数,表示 n 的所有数位之和。
示例1
输入:
12
输出:
3
说明:
将正整数 12 的各位相加,1+2=3。
示例2
输入:
-305
输出:
8
说明:
取绝对值后 305 的各位相加,3+0+5=8。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n, sum = 0;
cin >> n;
while (n) {
sum += n % 10;
n /= 10;
}
cout << sum;
}
Q38、牛牛数数
1、题目描述
描述
牛牛在酒桌上玩一个小游戏,第一个人从 1 开始数数,如果遇到数字中含有数字 4 或数字是 4 的倍数,则跳过这个数字报下一个,谁数错了就要罚酒一杯。
牛牛为了作弊,它想将所有符合规则的数字预先生成出来。请你帮助牛牛列出 1 到 n 之间所有既不包含数字 4 又不是 4 的倍数的整数,按升序输出。
输入描述:
在一行中输入一个正整数 n,满足 1 ≦ n ≦ 105。
输出描述:
按升序输出所有满足条件的整数,每个数字占一行。
示例1
输入:
9
输出:
1 2 3 5 6 7 9
说明:
在 1 到 9 中,数字 4 含有数字 4 且 4,8 为 4 的倍数,应跳过,剩余数字按升序输出。
2、代码实现
C++
#include <iostream>
using namespace std;
bool is4(int i) {
while (i) {
if (i % 10 == 4) {
return true;
}
i /= 10;
}
return false;
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
if (i % 4 == 0 || is4(i)) {
continue;
}
cout << i << endl;
}
}
08、数组
Q39、牛牛学数列6
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int helper(int n) {
if (n == 1) {
return 0;
}
if (n < 4) {
return 1;
}
return helper(n - 1) + 2 * helper(n - 2) + helper(n - 3);
}
int main() {
int n;
cin >> n;
cout << helper(n);
}
Q40、二维斐波那契数列
1、题目描述
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> dp(n + 1, vector(m + 1, 0));
dp[0][1] = 1;
int mod = 1e9 + 7;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % mod;
}
}
cout << dp[n][m];
}
Q41、神秘石像的镜像序列
1、题目描述
描述
在远古遗迹中,探险家们在石碑上发现了一系列关键信息,这些信息被刻录成一串整数序列,以数字 0 作为结束标志。
为了触发机关,需要将这串刻碑数字倒序读取(不包括结束标志 0)。
现在,请你编写程序,帮助探险家完成这一倒序读取的任务。
输入描述:
在一行内输入若干非负整数,以 0 作为结束标志,每两个整数之间用空格分隔。
保证整数个数不超过 100,且满足 (0 ≦ ai ≦ 231−1)。
输出描述:
在一行内按空格分隔输出倒序后的整数序列(不包含结束标志 00)。
示例1
输入:
8 15 3 42 7 0
输出:
7 42 3 15 8
说明:
在第一个样例中: ∙ ∙去掉结束标志 0,原序列为 [8,15,3,42,7]; ∙ ∙倒序后输出 7423158。
示例2
输入:
10 20 30 40 50 0
输出:
50 40 30 20 10
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
vector<int> v;
while (cin >> n) {
if (n == 0) {
break;
}
v.push_back(n);
}
for (int i = v.size() - 1; i >= 0; --i) {
cout << v[i] << " ";
}
return 0;
}
Q42、左侧严格小于计数
1、题目描述
描述
给定长度为 n 的整数序列 a1,a2,…,an,定义结果序列 b 为:
bi=∣{j∣1 ≤ j < i 且 aj < ai}∣,1 ≤ i ≤ n.
请计算并输出序列 b1,b2,…,bn。
输入描述:
第一行输入整数 n (1 ≤ n ≤ 100)。
第二行输入 n 个整数 a1,a2,…,an (0 ≤ ai ≤ 10),以空格分隔。
输出描述:
输出一行,包含 n 个整数 b1,b2,…,bn,以空格分隔。
示例1
输入:
5 1 2 3 4 5
输出:
0 1 2 3 4
示例2
输入:
6 4 4 2 1 3 5
输出:
0 0 0 0 2 5
说明:
∙ a5=3 时,左侧小于 3 的元素为 {4,4,2,1} 中的 {2,1},共 2 个; ∙ a6=5 时,左侧所有 5 个元素均小于 5,共 5 个。
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, i = 0;
cin >> n;
vector<int> v(n, 0);
while (cin >> n) {
int count = 0;
v[i] = n;
for (int j = 0; j < i; ++j) {
if (v[j] < v[i]) {
count++;
}
}
cout << count << " ";
++i;
}
return 0;
}
Q43、牛牛的数学作业
1、题目描述
2、代码实现
C++
#include <climits>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
while (n--) {
int size, num, minnum = INT_MAX, maxnum = INT_MIN, sum = 0;
cin >> size;
vector<int> v(size, 0);
for (int i = 0; i < size; ++i) {
cin >> num;
v[i] = num;
minnum = min(minnum, num);
maxnum = max(maxnum, num);
sum += num;
}
double variance = 0.0, average = sum * 1.0 / size ;
for (int i = 0; i < size; ++i) {
variance += (v[i] - average) * (v[i] - average);
}
printf("%d %.3lf\n", maxnum - minnum, variance / size);
}
}
Q44、数组计数维护
1、题目描述
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int t, n, k, num;
cin >> t;
while (t--) {
cin >> n >> k;
int s = 0, cnt = 0;
while (n--) {
cin >> num;
if (num >= k) {
s += num;
} else if (num == 0 && s >= 1) {
s--;
cnt++;
}
}
cout << cnt << endl;
}
return 0;
}
Q45、记数问题
1、题目描述
描述
试计算在区间 1 到 n (1 ≦ n ≦ 106) 的所有整数中,数字 x(0 ≦ x ≦ 9)共出现了多少次。
输入描述:
在一行中输入两个整数 n,x,用空格隔开。其中 n 表示区间上界,x 表示要统计的数字。
输出描述:
输出一个整数,表示数字 x 在区间 [1,n] 中出现的次数。
示例1
输入:
11 1
输出:
4
说明:
在 1,2,3,4,5,6,7,8,9,10,11 中,数字 1 出现了 4 次。
示例2
输入:
20 1
输出:
12
说明:
在区间 1 到 20 中,数字 1 出现在 1,10,11,12,13,14,15,16,17,18,19 中,共 12 次。
2、代码实现
C++
#include <iostream>
using namespace std;
int check(int i, int x) {
int ret = 0;
while (i) {
if (i % 10 == x) {
ret++;
}
i /= 10;
}
return ret;
}
int main() {
int n, x, count = 0;
cin >> n >> x;
for (int i = 1; i <= n; ++i) {
count += check(i, x);
}
cout << count;
return 0;
}
Q46、约瑟夫环
1、题目描述
描述
有 n 个人(编号 0∼n−1)围成一圈,从编号为 k 的人开始报数,报数依次为 1,2,…,m,报到 m 的人出队。下次从出队者的下一个人开始重新报数,循环往复,直到队伍中只剩最后一个人,该人即 “大王”。
给定三个正整数 n,k,m,请输出最后剩下的 “大王” 编号。
输入描述:
在一行中输入三个整数 n(2 ≦ n ≦ 100), k(0 ≦ k ≦ n−1),m(1 ≦ m ≦ 100),用空格隔开。
输出描述:
输出一个整数,表示最后剩下的 “大王” 编号。
示例1
输入:
5 1 2
输出:
3
说明:
初始队列编号为 [0,1,2,3,4],从编号 1 开始报数: 1(1),2(2)→2 出队,剩余 [0,1,3,4]; 3(1),4(2)→4 出队,剩余 [0,1,3]; 0(1),1(2)→1 出队,剩余 [0,3]; 3(1),0(2)→0 出队,剩余 [3],输出 3。
2、解题思路
使用队列(queue)来模拟整个过程:
- 将所有人按顺序放入队列
- 先让队列前k个人出队并重新入队,使编号k的人位于队首
- 然后开始报数:每次让 m-1个人出队并重新入队
- 第m个人直接出队(不重新入队)
- 重复这个过程直到队列只剩一人
3、代码实现
C++
#include <iostream>
#include <queue>
using namespace std;
int josephus(int n, int k, int m) {
queue<int> q;
// 初始化队列
for(int i=0; i<n; i++) {
q.push(i);
}
// 移动到起始位置k
for(int i=0; i<k; i++) {
q.push(q.front());
q.pop();
}
// 模拟报数过程
while(q.size() > 1) {
// 报数m-1个人
for(int i=0; i<m-1; i++) {
q.push(q.front());
q.pop();
}
// 第m个人出队
q.pop();
}
return q.front();
}
int main() {
int n, k, m;
cin >> n >> k >> m;
cout << josephus(n, k, m) << endl;
return 0;
}
Q47、校门外的树
1、题目描述
描述
某校大门外长度为 L 的马路上有一排树,每两棵相邻的树之间的间隔都是 1 米。我们可以把马路看成一个数轴,马路的一端在数轴 0 的位置,另一端在 L 的位置;数轴上的每个整数点,即 0,1,2,…,L,都种有一棵树。
由于马路上有一些区域要用来建地铁,这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。请你计算将这些树都移走后,马路上还有多少棵树。
输入描述:
第一行输入两个整数 L, M,用空格隔开,表示马路的长度和地铁施工区域数,满足 1 ≦ L ≦ 10000,1 ≦ M ≦ 100。
接下来 M 行,每行输入两个整数 li,ri,用空格隔开,表示第 i 个施工区域的起始点和终止点的坐标,满足 0 ≦ li ≦ ri ≦ L。
输出描述:
输出一个整数,表示移除所有施工区域内(包括端点)树后,剩余的树的总棵树数。
示例1
输入:
500 3 150 300 100 200 470 471
输出:
298
说明:
马路上共有 L+1=501 棵树。施工区域 [150,300] 中含有 151151 棵,[100,200] 中含有 101 棵,[470,471] 中含有 22 棵。三个区域的重叠部分 [150,200] 有 51 棵被重复移除,所以实际移除的树数为 151+101+2−51=203,因此剩余 501−203=298 棵。
示例2
输入:
10 2 2 5 4 8
输出:
4
说明:
马路上共有 11 棵树。区域 [2,5] 移除 4 棵,区域 [4,8] 移除 5 棵。重叠部分 [4,5] 有 2 棵被重复移除,所以实际移除 4+5−2=7 棵,剩余 11−7=4 棵。
2、代码实现
C++
#include <any>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int l, m;
cin >> l >> m;
vector<pair<int, int>> v(m);
for (int i = 0; i < m; ++i) {
cin >> v[i].first >> v[i].second;
}
sort(v.begin(), v.end());
vector<pair<int, int>> tmp;
for (const auto& kv : v) {
if (tmp.empty() || tmp.back().second < kv.first - 1) {
tmp.push_back(kv);
} else {
tmp.back().second = max(tmp.back().second, kv.second);
}
}
int ret = l + 1;
for (const auto& kv : tmp) {
ret -= (kv.second - kv.first + 1);
}
cout << ret << endl;
}
Q48、单组_二维数组
1、题目描述
描述
给定一个 n 行 m 列的二维正整数数组 {ai, j},其中 1 ≦ i ≦ n,1 ≦ j ≦ m,且 1 ≦ ai,j ≦ 109。
请计算数组中所有元素之和。
输入描述:
在一行上输入两个整数 n,m (1 ≦ n,m ≦ 103)。
接下来 n 行,每行输入 m 个整数 ai,1, ai,2 ,…,ai,m (1 ≦ ai,j ≦ 109)。
输出描述:
输出一个整数,表示二维数组所有元素之和。
示例1
输入:
3 4 1 2 3 4 5 6 7 8 9 10 11 12
输出:
78
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int size = n * m;
long long ret = 0, tmp = 0;
while (size--) {
cin >> tmp;
ret += tmp;
}
cout << ret;
}
Q49、上三角矩阵判定
1、题目描述
描述
牛牛想知道一个 n 阶方阵是否为上三角矩阵。所谓上三角矩阵,是指矩阵中主对角线以下的元素都为 0,其中主对角线是从矩阵左上角到右下角的连线。
请你判断给定的方阵是否满足这一性质。
输入描述:
在一行中输入一个整数 n (1 ≦ n ≦ 10)。
接下来 n 行,每行输入 n 个整数 ai,1, ai,2, …, ai,n (−109 ≦ ai,j ≦ 109),用空格分隔。
输出描述:
如果输入的方阵是上三角矩阵,则输出 “YES” (不含双引号)并换行;否则输出 “NO” (不含双引号)并换行。
示例1
输入:
3 1 2 3 0 4 5 0 0 6
输出:
YES
说明:
该矩阵主对角线以下元素均为 0,因此是上三角矩阵。
示例2
输入:
3 1 0 0 0 2 0 1 0 3
输出:
NO
说明:
该矩阵在第 3 行第 1 列元素为 1≠0,故不是上三角矩阵。
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> v(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> v[j];
}
for (int j = 0; j < i; ++j) {
if (v[j] != 0) {
cout << "NO";
return 0;
}
}
}
cout << "YES";
return 0;
}
Q50、矩阵转置
1、题目描述
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> v(n, vector(m, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> v[i][j];
}
}
for (int j = 0; j < m; ++j) {
for (int i = 0; i < n; ++i) {
cout << v[i][j] << " ";
}
cout << endl;
}
}
Q51、杨辉三角
1、题目描述
描述
杨辉三角形,又称帕斯卡三角形,第 i+1 行是二项式展开 (a+b)i 的系数。
三角形中任意元素等于上一行同列元素与上一行前一列元素之和。
下面给出杨辉三角形的前 4 行:
1
1 1
1 2 1
1 3 3 1
给定正整数 n,请输出杨辉三角形的前 n 行。
输入描述:
在一行输入一个整数 n (1 ≦ n ≦ 34)。
输出描述:
输出杨辉三角形的前 n 行。每一行从该行第一个元素开始,依次输出;每两个数之间用一个空格分隔。请不要在行末输出多余的空格。
示例1
输入:
4
输出:
1 1 1 1 2 1 1 3 3 1
说明:
当 n=4 时,杨辉三角形的前 4 行如上所示。
示例2
输入:
1
输出:
1
说明:
当 n=1 时,杨辉三角形只有第 1 行,元素为 1。
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<vector<int>> vv(n, vector(n, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
vv[i][j] = 1;
} else {
vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
}
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
cout << vv[i][j] << " ";
}
cout << endl;
}
}
Q52、扫雷
1、题目描述
描述
旺仔哥哥非常喜欢玩扫雷。现在给定一个初始的扫雷矩阵,其中用字符 *
表示雷,用 .
表示空白。
请你生成完整的扫雷矩阵。对于每个非雷格子,输出它周围八个方向中雷的个数;如果当前位置是雷,则仍输出 *
。
【名词解释】
雷: 用 *
表示的地雷;
邻格: 与当前位置在八个方向上相邻的格子;
完整扫雷矩阵: 填写完相应数字后的矩阵。
输入描述:
在一行中输入两个整数 n,m (1 ≦ n,m ≦ 1000),分别表示矩阵的行数和列数。
接下来 n 行,每行包含 m 个字符,每个字符是 *
或 .
,无空格分隔,表示初始的扫雷矩阵。
输出描述:
输出 n 行 m 列,表示完整的扫雷矩阵。对于每个位置:
- 如果该位置是雷,输出
*
; - 否则输出一个数字,该数字等于该位置周围八个邻格中雷的个数。
示例1
输入:
4 4 .... ..** *.*. .*.*
输出:
0122 13** *4*4 2*3*
说明:
∙ 第一行 `....` 中没有雷,对应 `0122`; ∙ 第二行 `..**` 中两个雷,对应 `13**`; ∙ 第三行 `*.*.` 中除自身外有 4 个雷邻格,对应 `*4*4`; ∙ 第四行 `.*.*` 对应 `2*3*`。
示例2
输入:
3 5 *...* ..... .*.*.
输出:
*101* 22222 1*2*1
说明:
∙ 第一行 `*...*`:两个雷分别在两端,对应 `*101*`; ∙ 第二行 `.....`:无雷,对应 `22222`; ∙ 第三行 `.*.*.`:两个雷对中间格子有 2 个邻雷,对应 `1*2*1`。
2、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<char>> matrix(n + 2, vector(m + 2, '.'));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> matrix[i][j];
}
}
int pos[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (matrix[i][j] == '*') {
cout << "*";
} else {
int count = 0;
for (int k = 0; k < 8; ++k) {
if (matrix[i + pos[k][0]][j + pos[k][1]] == '*') {
count++;
}
}
cout << count;
}
}
cout << endl;
}
}
09、字符串
Q53、年轻人不讲5的
1、题目描述
描述
这两个年轻人,不讲武德,来,骗!来,偷袭!我 69 岁的老同志。
这好吗?这不好!!!
我劝这位年轻人,耗子尾汁,好好反思。以后不要再犯这样的小聪明!
——混元形意太极门掌门人,马宝国
众所周知,年轻人不讲 5 的。作为一个年轻人,你应该自觉把数字中的 5 屏蔽掉。
给定一个只包含数字的非空字符串 s,字符串长度不超过 106。
请将字符串中所有字符 ‘5’ 替换为符号 ‘’‘’,并输出替换后的结果。
输入描述:
在一行输入一个只包含数字的非空字符串 ss,长度不超过 106。
输出描述:
输出一个字符串,为将所有字符 ‘5’ 替换为 ‘*’ 后的结果。
示例1
输入:
114514
输出:
114*14
说明:
输入中有一处字符 ‘5’,需要被替换为 ‘*’。
2、代码实现
C++
#include <iostream>
using namespace std;
int main() {
string s;
cin >> s;
for (const auto& ch : s) {
if (ch == '\n') {
break;
} else if (ch == '5') {
cout << '*';
} else {
cout << ch;
}
}
}
Q54、斗兽棋
1、题目描述
描述
牛牛和牛妹正在玩一个博弈游戏。每人可以选择一个棋子:elephant
、tiger
、cat
或 mouse
。
它们之间的胜负规则如下:
elephant
吃tiger
;tiger
吃cat
;cat
吃mouse
;mouse
吃elephant
;
如果一方的棋子能够吃掉另一方的棋子,则该方获胜;否则为平局。
给定牛牛和牛妹所出的棋子,请判断比赛结果。
输入描述:
在一行输入两个以空格分隔的字符串 s1,s2,其中 s1,s2∈{“elephant”,“tiger”,“cat”,“mouse”},分别代表牛牛和牛妹所出的棋子。
输出描述:
如果牛牛获胜,输出 win;如果牛妹获胜,输出 lose;如果平局,输出 tie。
示例1
输入:
tiger elephant
输出:
lose
说明:
牛牛出 `tiger`,牛妹出 `elephant`;大象吃老虎,牛牛落败,因此输出 `lose`。
2、代码实现
C++
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
// 定义胜负规则:key可以打败value
unordered_map<string, string> rules = {
{"elephant", "tiger"},
{"tiger", "cat"},
{"cat", "mouse"},
{"mouse", "elephant"}
};
if (s1 == s2) {
cout << "tie" << endl;
} else if (rules[s1] == s2) {
cout << "win" << endl;
} else if (rules[s2] == s1) {
cout << "lose" << endl;
} else {
cout << "tie" << endl;
}
return 0;
}
Q55、BFS
1、题目描述
描述
Bob 在学习了 DFS 后,自己又发明了一种新的搜(luan)索(gao)方法,叫做 BFS(Bob First Search)。
这种搜索被定义为:在一个字符串中,从前向后查找子串 “Bob” 第一次出现的位置(不区分大小写)。
输入描述:
在一行输入一个不含空格的字符串 SS,其长度为 ∣S∣,满足 (1≦∣S∣≦100)。
输出描述:
输出一个整数,表示子串 “Bob” 第一次出现的位置(下标从 0 开始)。
如果子串未出现,则输出 −1。
示例1
输入:
Bobob
输出:
0
说明:
字符串 "Bobob" 中开头即出现 "Bob",起始索引为 0。
示例2
输入:
bobby
输出:
0
说明:
字符串 "bobby" 中开头即出现 "bob",起始索引为 0。
示例3
输入:
body
输出:
-1
说明:
字符串 "body" 中不包含子串 "Bob"(忽略大小写),因此输出 −1。
2、代码实现
C++
#include <cctype>
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.size();
for (int i = 0; i < n - 2; ++i) {
if (s[i] == 'b' || s[i] == 'B') {
if ((s[i + 1] == 'o' || s[i + 1] == 'O') && (s[i + 2] == 'b' ||
s[i + 2] == 'B')) {
cout << i;
return 0;
}
}
}
cout << -1;
return 0;
}
Q56、凯撒加密
1、题目描述
描述
旺仔哥哥经常在牛客上刷题,但有一天他突然忘记了登录密码(他没有绑定邮箱或手机)。
他记得密码是通过对原文字符串 s 中的每个小写英文字母向后错位 n 次得到的。字母 z 向后错位一次会变为 a,以此类推循环。
现给出原文字符串 s 和错位次数 n,请帮助旺仔哥哥计算出最终密码。
输入描述:
第一行输入一个整数 n (1 ≦ n ≦ 100),表示错位次数。
第二行输入一个由小写英文字母组成的字符串 s (1 ≦ ∣s∣ ≦ 103),表示原文字符串。
输出描述:
输出一个字符串,表示对 ss 中每个字符向后错位 n 次后得到的密码字符串。
示例1
输入:
1 abcdefg
输出:
bcdefgh
说明:
∙ 每个字母向后移动 1 次,"a"→"b","b"→"c",…,"g"→"h",得到 "bcdefgh"。
示例2
输入:
2 xyz
输出:
zab
说明:
∙ 每个字母向后移动 2 次,"x"→"z","y"→"a","z"→"b",得到 "zab"。
2、代码实现
C++
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
string s;
cin >> s;
for (auto& ch : s) {
ch = (ch - 'a' + n) % 26 + 'a';
}
cout << s;
return 0;
}
Q57、无限长正整数排列字符串
1、题目描述
描述
定义无限字符串 S=“123456789101112…” ,即将所有正整数依次拼接得到。
珂朵莉想知道该字符串的第 n 个字符是什么。
输入描述:
在一行中输入一个整数 n (1 ≦ n ≦ 1000)。
输出描述:
输出一个数字,表示字符串 S 的第 n 个字符。
示例1
输入:
3
输出:
3
说明:
当 n=3 时,S="123......,其第 3 个字符为 ’3’。
示例2
输入:
11
输出:
0
说明:
当 n=11 时,S="12345678910......,其第 11 个字符为 ’0’
2、解题思路
- 数字位数分组:
- 1位数(1-9):9个数字,共9位
- 2位数(10-99):90个数字,共180位
- 3位数(100-999):900个数字,共2700位
- 依此类推…
- 定位步骤:
- 确定n所在的数字位数范围
- 计算具体是哪个数字
- 找出该数字的具体某一位
3、代码实现
C++
#include <iostream>
#include <string>
using namespace std;
int findNthDigit(int n) {
int digits = 1; // 当前数字位数
long long count = 9; // 当前位数数字的总位数
int start = 1; // 当前位数数字的开始数字
// 1. 确定数字位数
while (n > count) {
n -= count;
digits++;
start *= 10;
count = 9 * start * digits;
}
// 2. 确认具体数字
int num = start + (n - 1) / digits;
// 3. 确定数字中的具体位
int digit_pos = (n - 1) % digits;
return to_string(num)[digit_pos] - '0';
}
int main() {
int n;
cin >> n;
cout << findNthDigit(n) << endl;
}
Q58、简写单词
1、题目描述
描述
规定一种对于复合词的简写方式:只保留每个组成单词的首字母,并将首字母大写后再连接在一起。
例如:
- “College English Test” 可简写为 “CET”;
- “Computer Science” 可简写为 “CS”;
- “I am Bob” 可简写为 “IAB”。
现在输入一个由若干单词组成的复合词,请输出它的简写形式。
输入描述:
在一行中输入一个复合词,由若干单词组成。
- 单词数 sum 满足 1 ≦ sum ≦ 100;
- 每个单词长度 len 满足 1 ≦ len ≦ 50;
- 单词之间由单个空格分隔;
- 每个单词仅由大小写英文字母组成。
输出描述:
输出一个字符串,为复合词的简写形式。
简写方式为:取每个单词的首字母并将其转换为大写,然后按原单词顺序依次连接。
请不要输出多余的空格或换行。
示例1
输入:
International Collegiate Programming Contest
输出:
ICPC
示例2
输入:
Super mySQL
输出:
SM
2、代码实现
C++
#include <cctype>
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
while (cin >> s) {
cout << (char)(toupper(s[0]));
}
return 0;
}
Q59、牛牛的考试
1、题目描述
描述
众所周知,牛牛是一个 “1+1” 都能回答错误的小可爱,因此经常遭受旺仔哥哥的头槌惩罚。
今天,旺仔哥哥出了一场选择题测验,牛牛决定按照坊间传说来猜答案:
- 三长一短选最短;
- 三短一长选最长;
- 参差不齐就选 C。
【名词解释】
【三长一短】三长一短 是指在四个选项长度中恰有一个选项的长度严格小于另外三个选项;
【三短一长】三短一长 是指在四个选项长度中恰有一个选项的长度严格大于另外三个选项;
【参差不齐】参差不齐 是指既不满足“三长一短”也不满足“三短一长”的情况。
输入描述:
第一行输入一个整数 T (1 ≦ T ≦ 500),表示题目数。
接下来共有 T 道题,每道题由 4 行字符串组成,分别对应选项 A、B、C、D;每个字符串长度不超过 600,由可见字符(字母、数字、符号等)组成。
输出描述:
对于每道题,输出按照上述坊间传说应选的选项字母(A、B、C 或 D),每个字母独占一行。
示例1
输入:
3 A.3.141592653589 B.2.718281828459 C.0.618033988749 D.0.577215664901532860 A.wo_shi_cuo_de B.wo_bu_dui C.wo_shi_dui_de D.C_shi_dui_de A.3.141592653589 B.2.718281828459 C.0.618033988749 D.0.577215664901
输出:
D B C
说明:
∙ 第 1 道题中,选项长度分别为 14、14、14、18,恰有一个最长,符合“三短一长”,选最长 D; ∙ 第 2 道题中,选项长度分别为 13、9、13、12,恰有一个最短,符合“三长一短”,选最短 B; ∙ 第 3 道题中,四个选项长度均相同,属于“参差不齐”,选 C。
2、代码实现
C++
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
int main() {
int n;
cin >> n;
while (n--) {
string s;
unordered_map<int, vector<char>> hash;
int minlen = INT32_MAX;
int maxlen = INT32_MIN;
int len = 0;
for (char i = 'A'; i <= 'D'; ++i) {
cin >> s;
len = s.size();
hash[len].push_back(i);
minlen = min(minlen, len);
maxlen = max(maxlen, len);
}
if (hash[minlen].size() == 1) {
cout << hash[minlen][0] << endl;
} else if (hash[maxlen].size() == 1) {
cout << hash[maxlen][0] << endl;
} else {
cout << "C" << endl;
}
}
return 0;
}
Q60、添加逗号
1、题目描述
描述
给定一个正整数 N (1 ≦ N ≦ 2 ×109)。
现在需要将其转换为千分位格式,即从整数最低位开始,每三位数字插入一个英文逗号,以提高可读性。
例如,对于 980364535,转换后为 980,364,535。
请编写程序完成该格式转换。
输入描述:
在一行中输入一个整数 N (1 ≦ N ≦ 2×109)。
输出描述:
输出一个字符串,表示将 N 转换为千分位格式后的结果。
请不要输出多余的空格或换行。
示例1
输入:
980364535
输出:
980,364,535
示例2
输入:
6
输出:
6
2、解题思路
- 将整数转换为字符串:方便逐位处理
- 从右向左遍历:每三位插入一个逗号
- 处理边界情况:避免在最左侧添加多余的逗号
- 构建结果字符串:将所有字符和逗号组合
3、代码实现
C++
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
string s = to_string(n);// 将整数转为字符串
string ret;
int count = 0;// 记录当前处理了多少位数字
// 从右向左遍历
for (int i = s.length() - 1; i >= 0; --i) {
ret.push_back(s[i]);
count++;
// 每三位添加一个逗号,且不是最后一位
if (count % 3 == 0 && i != 0) {
ret.push_back(',');
}
}
// 反转结果字符串
reverse(ret.begin(), ret.end());
cout << ret;
return 0;
}
Q61、字符串操作
1、题目描述
描述
给定长度为 n 的只含小写字母的字符串 s,以及正整数 m 次操作。
每次操作给定两个整数 ℓ,r 和两个小写字母 c1,c2;将字符串 s 在区间 [ℓ,r]内的所有字符 c1 替换为 c2。
按顺序执行完所有操作后,输出最终的字符串。
输入描述:
在一行输入两个整数 n,m (1 ≦ n,m ≦ 100)。
接下来一行输入一个只含小写字母的字符串 s,长度为 n。
再接下来 m 行,每行输入两个整数 ℓ,r 和两个字符 c1,c2,用空格分隔,其中 1 ≦ ℓ ≦ r ≦ n,c1,c2 为小写字母。
输出描述:
输出一个只含小写字母的字符串,表示执行完所有操作后的最终字符串。
示例1
输入:
5 3 wxhak 3 3 h x 1 5 x a 1 3 w g
输出:
gaaak
说明:
∙ 初始字符串为 `wxhak`; ∙ 第 1 次操作将位置 3 上的 `h` 替换为 `x`,得到 `wxxak`; ∙ 第 2 次操作将位置 1 至 5 的 `x` 替换为 `a`,得到 `waaak`; ∙ 第 3 次操作将位置 1 至 3 的 `w` 替换为 `g`,得到 `gaaak`。
2、代码实现
C++
#include <iostream>
#include <string>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
string s;
cin >> s;
while (m--) {
int left, right;
cin >> left >> right;
// cout << left << right << endl;
char c1, c2;
cin >> c1 >> c2;
// cout << c1 << c2 << endl;
for (int i = left; i <= right; ++i) {
if (s[i - 1] == c1) {
s[i - 1] = c2;
}
}
}
cout << s;
return 0;
}
10、函数与函数交互题
Q62、a 加 b 问题(函数)
1、题目描述
描述
你需要编写一个函数,实现正整数之间的加法功能。
具体而言,你的函数会接收两个参数:a,b(−109 ≦ a,b ≦ 109),你需要计算出这两个数的和,并将这个结果作为函数的返回值。
示例1
输入:
114,514
返回值:
628
说明:
114 + 514 = 628
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
int addTwoInteger(int Integer1, int Integer2) {
return Integer1 + Integer2;
}
};
Q63、a 乘 b 问题(函数)
1、题目描述
描述
你需要编写一个函数,实现正整数之间的乘法功能。
具体而言,你的函数会接收两个参数:a,b(−109 ≦ a,b ≦ 109),你需要计算出这两个数的乘积,并将这个结果作为函数的返回值。
示例1
输入:
1,8
返回值:
8
示例2
输入:
11,11
返回值:
121
示例3
输入:
1000000000,2000000000
返回值:
2000000000000000000
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算两个参数的乘积
* @param Number1 int整型
* @param Number2 int整型
* @return long长整型
*/
long long aTimesB(int Number1, int Number2) {
return (long long)Number1 * Number2;
}
};
Q64、平方根
1、题目描述
描述
你需要实现一个函数,接受的参数为一个非负整数 n(0 ≤ n ≤ 104),你需要计算出 n 的值,并作为该函数返回值,使得该函数可以实现求一个正整数的平方根的功能。
由于浮点数计算存在误差,只要你的返回值与n 的真实值之间的误差在 10−5 以内,则你的答案就会被视为是正确的。
示例1
输入:
0
返回值:
0.00000000000
示例2
输入:
2
返回值:
1.414213562373095048
示例3
输入:
8964
返回值:
94.67840302835700027
2、代码实现
C++
#include <cmath>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求非负整数 n 的平方根
* @param n int整型 你需要求 n 的平方根
* @return double浮点型
*/
double findSqrt(int n) {
return (double)sqrt(n);
}
};
Q65、一元二次方程
1、题目描述
描述
给你一个一元二次方程,你需要判断它是否有实数解。
具体而言,你需要实现一个函数,接受参数有三个整数 a,b,c(−103 ≤ a,b,c ≤ 103),你需要返回一个布尔值,表示判断一元二次方程 a⋅x2+b⋅x+c=0 是否有实数解的结果,如果有解则返回 true,无解则返回 false。
示例1
输入:
0,0,0
返回值:
true
示例2
输入:
1,2,2
返回值:
false
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 判断二元一次方程组是否有解
* @param a int整型 二次项系数
* @param b int整型 一次项系数
* @param c int整型 常数项
* @return bool布尔型
*/
bool judgeSolutions(int a, int b, int c) {
return b * b - 4 * a * c >= 0;
}
};
Q66、第一宇宙速度
1、题目描述
描述
两个间距为 r,质量分别为 M 和 m 的质点之间的万有引力公式为:F万有引力 = G⋅M⋅m/r2,其中 G 为万有引力常量,在本题中可以假设该值为 6.67×10−11。
质量为 m 的质点在距离旋转中心距离为 r 的位置保持垂直于旋转轴的速率为 v 的匀速圆周运动,所需要的向心力公式为:F向心力=m⋅v2/r。
假如一个质量为 M 的星球,有一个质量为 m 的物体在星球表面运动,则此时这两个质点之间的距离为 r。如果这个物体持续加速,则维持不脱离星球表面所需要的向心力越来越大,直至向心力与万有引力相等的临界点,这个物块就即将脱离星球表面了,此时的临界速度即为这个星球的第一宇宙速度。
假如给你一个星球的质量 M 以及这个星球的半径 r,你能求出这个星球的第一宇宙速度吗?
你需要实现一个函数,接受的参数为两个浮点数,依次为星球的质量 M(0 < M ≦ 1018) 以及这个星球的半径 r(0 < r ≦ 109)。你需要返回一个浮点数,表示这个星球的第一宇宙速度。
示例1
输入:
1000.00000,1.000000
返回值:
0.000258263431402899
备注:
由于浮点数存在误差,只要你的返回值与真实值之间的误差在 10−5 以内,就会被认为是正确的。
2、解题思路
- 公式推导:直接从给定的物理公式出发,推导第一宇宙速度的表达式。
- 数值计算:
- 使用题目给定的万有引力常量 G = 6.67×10−11
- 计算 G × M / r
- 最后取平方根得到第一宇宙速度
- 浮点数处理:注意数值精度和可能的浮点误差。
3、代码实现
C++
#include <cmath>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算星球的第一宇宙速度
* @param M double浮点型 星球的质量
* @param r double浮点型 星球的半径
* @return double浮点型
*/
double firstSpeed(double M, double r) {
return sqrt(6.67e-11 * M / r);
}
};
Q67、求阶乘
1、题目描述
描述
你需要实现一个函数,接受的参数为一个非负整数 n(1 ≤ n ≤ 104),你需要计算出 n! 的值,并作为该函数返回值,使得该函数可以实现求一个正整数的阶乘的功能。
如果你不知道阶乘是什么,这里给出一个公式 n!=n×(n−1)×(n−2)×⋯×1。
由于结果可能很大,你需要返回最终的计算结果对 109+7 取模的结果即可。
示例1
输入:
1
返回值:
1
示例2
输入:
2
返回值:
2
示例3
输入:
3
返回值:
6
备注:
提示,取模运算对加法运算满足交换律和结合律,所以在计算过程中多次取模得到的计算结果,和全部计算都完成后得到的计算结果是相同的。
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算 n 的阶乘
* @param n int整型
* @return int整型
*/
const int MOD = 1e9 + 7;
int factorialOfN(int n) {
long long ret = 1;
for (int i = 1; i <= n; ++i) {
ret = ret * i % MOD;
}
return ret;
}
};
Q68、凯撒解密
1、题目描述
描述
还记得吗?旺仔哥哥经常上牛客刷题,但是,有一天登陆时却突然发现忘记密码了。
当时,旺仔哥哥告诉过你,他虽然忘记密码,但他还记得密码是一个仅由小写字母构成的字符串,且密码是由她女朋友的表白信构成的字符串 s(1 ≤ ∣s∣ ≤ 103) 中每个字母向后错位 n (1 ≤ n ≤ 100) 次形成的。z 的下一个字母是 a,如此循环。
当时你帮助旺仔哥哥求出了密码,旺仔哥哥对你千恩万谢。但是你突然想到,如果把密码反向解密回去,岂不是就可以看到旺仔哥哥女朋友的表白信了!!?
于是,你打算实现一个函数,接受的参数有两个,第一个为加密后的密码字符串,第二个为一个正整数,表示加密过程中每个字母向后错位的次数。你的函数需要计算出加密前的原文字符串,并作为函数的返回值。
示例1
输入:
"abcd",1
返回值:
"zabc"
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 进行凯撒解密
* @param password string字符串 旺仔哥哥的密码
* @param n int整型 每个字符加密过程中错位的次数
* @return string字符串
*/
string decodeWangzai(string password, int n) {
for (auto& ch : password) {
ch = ((ch - 'a' - n) % 26 + 26) % 26 + 'a';
}
return password;
}
};
11、序列
Q69、【模板】序列操作
1、题目描述
描述
你需要维护一个初始为空的整数序列,支持以下 8 种操作:
1. 输入格式为 1 x
,表示向序列末尾增加一个整数 x(1 ≦ x ≦ 109);
2. 输入格式为 2
,表示删除序列末尾的元素(保证此时序列非空);
3. 输入格式为 3 i
,表示输出序列中下标为 i(起始下标为 0)的元素;
4. 输入格式为 4 i x
,表示在下标为 i 的元素与下标为 i+1 的元素之间插入整数 x(起始下标为 0,0 ≦ i < ∣序列∣,1 ≦ x ≦ 109);
5. 输入格式为 5
,表示将序列按照从小到大升序排序;
6. 输入格式为 6
,表示将序列按照从大到小降序排序;
7. 输入格式为 7
,表示输出当前序列的长度;
8. 输入格式为 8
,表示输出当前整个序列。
输入描述:
第一行输入一个整数 q (1 ≦ q ≦ 104),表示操作总次数。
接下来 q 行,每行输入一种操作,格式如题目描述所示。
输出描述:
对于每次操作类型 3,在一行输出对应的元素;
对于每次操作类型 7,在一行输出当前序列的长度;
对于每次操作类型 8,在一行输出由当前序列所有元素组成的序列,元素之间用空格分隔。
示例1
输入:
5 1 8 1 9 7 1 6 8
输出:
2 8 9 6
说明:
∙ ∙操作 `1 8` 后序列为 {8}{8}; ∙ ∙操作 `1 9` 后序列为 {8,9}{8,9}; ∙ ∙操作 `7` 输出长度 22; ∙ ∙操作 `1 6` 后序列为 {8,9,6}{8,9,6}; ∙ ∙操作 `8` 输出序列 `8 9 6`。
示例2
输入:
8 1 5 1 3 1 7 3 1 4 1 4 8 5 8
输出:
3 5 3 4 7 3 4 5 7
说明:
∙ ∙序列依次变为 {5},{5,3},{5,3,7}{5},{5,3},{5,3,7}; ∙ ∙操作 `3 1` 输出下标 11 的元素 33; ∙ ∙操作 `4 1 4` 在下标 11 与 22 之间插入 44,序列变为 {5,3,4,7}{5,3,4,7}; ∙ ∙操作 `8` 输出序列 `5 3 4 7`; ∙ ∙操作 `5` 升序排序后序列变为 {3,4,5,7}{3,4,5,7}; ∙ ∙操作 `8` 输出序列 `3 4 5 7`。
2、代码实现
C++
#include <algorithm>
#include <iostream>
#include <vector>
// #include <algorithm>
using namespace std;
int main() {
int n, code, i, x;
cin >> n;
vector<int> v;
while (n--) {
cin >> code;
if (code == 1) {
cin >> x;
v.push_back(x);
} else if (code == 2) {
v.pop_back();
} else if (code == 3) {
cin >> i;
cout << v[i] << endl;
} else if (code == 4) {
cin >> i >> x;
v.push_back(0);
int j = v.size();
for (; j > i + 1; --j) {
v[j] = v[j - 1];
}
v[j] = x;
} else if (code == 5) {
sort(v.begin(), v.end());
} else if (code == 6) {
sort(v.begin(), v.end(), greater<int>());
} else if (code == 7) {
cout << v.size() << endl;
} else if (code == 8) {
for (const auto& e : v) {
cout << e << " ";
}
cout << endl;
}
}
}
Q70、求峰谷点数
1、题目描述
描述
给定一个由正整数构成的序列 a,如果其中某一个元素 ai 满足 ai−1 < ai > ai+1(0 < i < n−1),则称该元素 ai 为数组的一个峰点;如果其中某一个元素 ai 满足 ai−1 > ai < ai+1(0 < i < n−1),则称该元素 ai 为数组的一个谷点。
你需要实现一个函数,接受的参数为一个由正整数构成的序列 a,你需要求出这个序列中峰点和谷点的总数,并作为该函数的返回值。
示例1
输入:
[1,1,4,5,1,4]
返回值:
2
备注:
数据保证序列 a 中元素的数量不超过 105 个。
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求序列a中的峰、谷点的个数
* @param a int整型vector 序列a
* @return int整型
*/
int countPeakPoint(vector<int>& a) {
int count = 0;
for (int i = 1; i < a.size() - 1; ++i) {
if ((a[i - 1] > a[i] && a[i + 1] > a[i]) || (a[i - 1] < a[i] && a[i + 1] < a[i])) {
count++;
}
}
return count;
}
};
Q71、旺仔哥哥挤地铁
1、题目描述
描述
一路地铁依次经过 n 个站点,编号依次为 1∼n。地铁从第 i 个站点到第 i+1 个站点需要用 ti 秒,而地铁到第 i 站时会停 si 秒。
旺仔哥哥想从第 x 站坐地铁到第 y 站。那么他在地铁上的最长时间是多少?
注:最长时间,即地铁刚到第 x 站就上地铁,地铁即将离开第 y 站才下地铁的情况下,旺仔哥哥在地铁上的时间。单位为秒。
你需要实现一个函数,接受的参数有四个,分别是:
- 序列 t1, t2, …, tn−1 (1 ≦ ti ≦ 103 ,表示地铁在相邻两站之间的用时。
- 序列 s1, s2, …, sn (1 ≦ ti ≦ 103),表示地铁在每一站的停靠时间。
- 两个正整数 x, y (1 ≦ x,y ≦ 103),表示旺仔哥哥想从第 x 站坐到第 y 站。
返回值为一个正整数,即旺仔哥哥在地铁上的最长时间,单位为秒。
示例1
输入:
[150,180,170],[35,32,33,34],2,4
返回值:
449
说明:
旺仔哥哥在地铁刚到第 2 站就上了地铁,接下来地铁经过如下流程: - 先在第 2 站停靠 32 秒。 - 然后用 180 秒开到第 3 站。 - 在第 3 站停靠 33 秒。 - 然后用 170 秒开到第 4 站。 - 最后在第 4 站停靠 34 秒。 然后旺仔哥哥下车。在地铁上的总时间是 32+180+33+170+34=449 秒。
示例2
输入:
[300,300,300,300],[40,40,40,40,40],2,4
返回值:
720
示例3
输入:
[150,180,170],[35,32,33,34],1,4
返回值:
634
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算旺仔哥哥在地铁上的最长停留时间
* @param t int整型vector 序列 t,表示地铁在相邻两站之间的用时
* @param s int整型vector 序列 s,表示地铁在每一站的停靠时间
* @param x int整型 旺仔哥哥想从第 x 站出发
* @param y int整型 旺仔哥哥想坐到第 y 站
* @return int整型
*/
int countLongestSubwayTime(vector<int>& t, vector<int>& s, int x, int y) {
int ret = 0;
int i = x;
for (; i < y; ++i) {
ret += t[i - 1];
ret += s[i - 1];
}
ret += s[i - 1];
return ret;
}
};
Q72、逗号整合器
1、题目描述
描述
我们写的程序通常用空格隔开输入的数字,然而在一些数据处理软件当中,默认用逗号隔开数字,因此通常需要一个程序辅助实现格式转换。
你需要完成这样一个函数。参数为一个由若干个整数构成的序列,你需要整理出一个将序列中的数字以逗号隔开从而得到的字符串,并将其作为该函数的返回值。
示例1
输入:
[1,3,5]
返回值:
"1,3,5"
示例2
输入:
[1]
返回值:
"1"
示例3
输入:
[]
返回值:
""
备注:
数据保证:序列中的整数个数小于 105,序列中的所有元素 x 都满足 −100≤x≤100。
2、代码实现
C++
#include <string>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 整理出一个将序列中的数字以逗号隔开从而得到的字符串
* @param a int整型vector 需要整理的序列 a
* @return string字符串
*/
string commaTransformer(vector<int>& a) {
if (a.size() == 0) {
return "";
}
string s = "";
for (int i = 0; i < a.size() - 1; ++i) {
s += to_string(a[i]);
s += ",";
}
s += to_string(a[a.size() - 1]);
return s;
}
};
Q73、旺仔哥哥转圈圈
1、题目描述
描述
有 n 个小朋友排成一圈,按照顺时针顺序依次被编号为 1∼n,每个小朋友衣服上都有一个数字,第 ii 个小朋友的数字是 ai。
旺仔哥哥想要选出一个小朋友,于是他先站在 1 号小朋友旁边,然后以如下方式移动 m 次:
逆时针走过「当前小朋友衣服上的数字」数量个小朋友。
旺仔哥哥想知道,他最后会站在哪位小朋友旁边。
你需要实现一个函数,包含以下两个参数:
-
一个序列 a,第 i 个小朋友的数字是 ai。
-
一个正整数 m,表示旺仔哥哥的移动次数。
你需要计算出旺仔哥哥最后会站在哪位小朋友旁边,并将这个结果作为该函数的返回值。
示例1
输入:
[2,1,4,5,2,3],3
返回值:
5
说明:
初始时,旺仔哥哥站在 1 号小朋友旁边。 第 1 次移动前,1 号小朋友衣服上的数字 a1=2,因此旺仔哥哥需要逆时针走过 2 个小朋友。旺仔哥哥走到 5 号小朋友旁边。 第 2 次移动前,5 号小朋友衣服上的数字 a5=2,因此旺仔哥哥需要逆时针走过 2 个小朋友。旺仔哥哥走到 3 号小朋友旁边。 第 3 次移动前,3 号小朋友衣服上的数字 a3=4,因此旺仔哥哥需要逆时针走过 4 个小朋友。旺仔哥哥走到 5 号小朋友旁边。 最终旺仔哥哥站在 5 号小朋友旁边。
备注:
数据保证 1≤n,m,ai≤104。
2、解题思路
-
模拟移动过程:
- 初始位置为 1(即 current=1)。
- 每次移动时,逆时针走 acurrent 个小朋友。
- 因为小朋友是围成一圈的,所以移动时需要处理环形结构,可以用模运算来定位。
-
环形处理:
-
每次移动 acurrent 步,相当于逆时针方向移动,即相当于顺时针方向移动 n - acurrent 步。
-
当前位置的计算公式:
current = (current − acurrent − 1) mod n+1
- 这里 current − acurrent − 1 是为了处理逆时针移动的偏移,然后通过模 n 和 +1 来调整到 1∼n 的范围。
-
-
复杂度优化:
- 直接模拟每次移动的复杂度是 O(m),对于 m ≤ 106 是可以接受的。
- 如果 m 非常大(如 1018),可能需要找规律或周期性来优化,但本题的 m 是合理的。
3、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算出旺仔哥哥最后会站在哪位小朋友旁边
* @param a int整型vector 第 i 个小朋友的数字是 a_i
* @param m int整型 表示旺仔哥哥的移动次数
* @return int整型
*/
int stopAtWho(vector<int>& a, int m) {
int ret = 0;
int n = a.size();
while (m--) {
ret = ((ret - a[ret]) % n + n) % n;
}
return ret + 1;
}
};
Q74、向量点乘
1、题目描述
描述
给定两个三维向量,你需要求出这两个向量的点乘的结果。
你需要实现一个函数,接受两个参数,均为由三个整数构成的向量。你需要计算出这两个向量点乘的结果,并将这个结果作为该函数的返回值。
示例1
输入:
[1,1,4],[5,1,4]
返回值:
22
备注:
数据保证各个向量的坐标的绝对值均小于 104。
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算两个三维向量的点乘结果
* @param vector1 int整型vector 第一个向量
* @param vector2 int整型vector 第二个向量
* @return int整型
*/
int dotTime(vector<int>& vector1, vector<int>& vector2) {
return vector1[0] * vector2[0] + vector1[1] * vector2[1] + vector1[2] * vector2[2];
}
};
Q75、向量叉乘
1、题目描述
描述
给定两个三维向量,你需要求出这两个向量的叉乘的结果。
你需要实现一个函数,接受两个参数,均为由三个整数构成的向量。你需要计算出这两个向量叉乘的结果,并将这个结果作为该函数的返回值。
示例1
输入:
[1,0,1],[0,1,0]
返回值:
[-1,0,1]
备注:
数据保证各个向量的坐标的绝对值均小于 104。
2、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算出这两个向量叉乘的结果
* @param vector1 int整型vector
* @param vector2 int整型vector
* @return int整型vector
*/
vector<int> crossTimes(vector<int>& vector1, vector<int>& vector2) {
vector<int> ret(3);
ret[0] = vector1[1] * vector2[2] - vector1[2] * vector2[1];
ret[1] = vector1[2] * vector2[0] - vector1[0] * vector2[2];
ret[2] = vector1[0] * vector2[1] - vector1[1] * vector2[0];
return ret;
}
};
12、栈
Q76、【模板】栈的操作
1、题目描述
描述
实现一个初始为空的栈,支持以下操作:
- push(x):将整数 x 入栈;
- pop():若栈非空,则删除栈顶元素;否则输出 Empty;
- query():若栈非空,则输出栈顶元素;否则输出 Empty;
- size():输出栈中元素的数量。
输入描述:
输入的第一行包含一个整数 n (1 ≦ n ≦ 105),表示操作总数;
接下来的 n 行,每行描述一条操作,格式如下:
- “push x”,将整数 x (−109 ≦ x ≦ 109) 入栈;
- “pop”、“query”、“size”,执行对应操作。
输出描述:
对每组数据,按操作顺序依次输出所有需要输出的结果,每次输出占一行。
示例1
输入:
7 push 1 push 2 size query pop pop query
输出:
2 2 Empty
说明:
执行 push 1、push 2 后,size 输出 2,query 输出 2。 两次 pop 后栈被清空,query 输出 Empty。
2、代码实现
C++
#include <cstdlib>
#include <iostream>
#include <queue>
#include <stack>
#include <string>
using namespace std;
template <class T>
class Stack {
public:
Stack() {
_data = (T*)malloc(4 * sizeof(T));
_top = 0;
_capacity = 4;
}
~Stack() {
free(_data);
_top = _capacity = 0;
}
void push(T x) {
if (_top == _capacity) {
_capacity *= 2;
_data = (T*)realloc(_data, _capacity * sizeof(T));
}
_data[_top++] = x;
}
void pop() {
_top--;
}
T query() {
return _data[_top - 1];
}
size_t size() {
return _top;
}
private:
T* _data;
size_t _top;
size_t _capacity;
};
int main() {
int n, x;
cin >> n;
string s;
Stack<int> st;
while (n--) {
cin >> s;
if (s == "push") {
cin >> x;
st.push(x);
} else if (s == "pop") {
if (st.size() == 0) {
cout << "Empty" << endl;
} else {
st.pop();
}
} else if (s == "query") {
if (st.size() == 0) {
cout << "Empty" << endl;
} else {
cout << st.query() << endl;
// cout << st.top() << endl;
}
} else if (s == "size") {
cout << st.size() << endl;
}
}
return 0;
}
Q77、括号配对问题
1、题目描述
描述
给定一个字符串 S,请检查字符串中仅由括号字符 [
、]
、(
、)
组成的子序列是否构成合法括号序列。合法括号序列的定义如下:
-
空序列是合法括号序列;
-
如果 A 是合法括号序列,则
(A)
和[A]
都是合法括号序列; -
如果 A 和 B 都是合法括号序列,则它们的拼接 AB 也是合法括号序列。
字符串 S 可能包含其他字符,但只需考虑括号部分,忽略其他字符。
输入描述:
在一行中输入一个字符串 S,长度 1 ≦ ∣S∣ ≦ 104,由可见字符组成。
输出描述:
如果字符串 S 中的括号部分能构成合法括号序列,则输出 true
;否则输出 false
。
示例1
输入:
abcd(])[efg
输出:
false
说明:
提取括号 `(`、`)`、`[`、`]` 后为 `(])[`,不是合法括号序列。
示例2
输入:
a[x(y)z]
输出:
true
说明:
提取括号后为 `[()]`,是合法括号序列。
2、解题思路
-
提取括号子序列:
- 遍历字符串 S,筛选出所有
[
,]
,(
,)
字符,组成一个新的子序列。
- 遍历字符串 S,筛选出所有
-
检查子序列的合法性:
- 使用栈数据结构来检查括号的匹配。
- 遍历子序列,遇到左括号
(
或[
时入栈。 - 遇到右括号
)
或]
时,检查栈顶的左括号是否与之匹配:)
必须匹配(
。]
必须匹配[
。
- 如果匹配,弹出栈顶的左括号;否则,序列不合法。
- 遍历结束后,如果栈为空,则序列合法;否则不合法。
3、代码实现
C++
#include <iostream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
bool is_value(const vector<char>& v) {
stack<char> s;
for (const auto& c : v) {
if (c == '(' || c == '[') {
s.push(c);
} else {
if (s.empty()) {
return false;
}
char top = s.top();
s.pop();
if ((c == ')' && top != '(') || (c == ']' && top != '[')) {
return false;
}
}
}
return s.empty();
}
bool check(const string& s) {
vector<char> v;
for (const auto& c : s) {
if (c == '(' || c == ')' || c == '[' || c == ']') {
v.push_back(c);
}
}
return is_value(v);
}
int main() {
string s;
cin >> s;
cout << (check(s) ? "true" : "false") << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:O(n),遍历字符串两次(提取括号和检查合法性)。
- 空间复杂度:O(n),栈和
vector
的空间最坏为 O(n)。
Q78、包含min函数的栈
1、题目描述
描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。
此栈包含的方法有:
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素
数据范围:操作数量满足 0 ≤ n ≤ 300 ,输入的元素满足 ∣val∣ ≤ 10000
进阶:栈的各个操作的时间复杂度是 O(1) ,空间复杂度是 O(n)
示例:
输入: [“PSH-1”,“PSH2”,“MIN”,“TOP”,“POP”,“PSH1”,“TOP”,“MIN”]
输出: -1,2,1,-1
解析:
"PSH-1"表示将-1压入栈中,栈中元素为-1
"PSH2"表示将2压入栈中,栈中元素为2,-1
“MIN”表示获取此时栈中最小元素==>返回-1
"TOP"表示获取栈顶元素==>返回2
"POP"表示弹出栈顶元素,弹出2,栈中元素为-1
"PSH1"表示将1压入栈中,栈中元素为1,-1
"TOP"表示获取栈顶元素==>返回1
“MIN”表示获取此时栈中最小元素==>返回-1
示例1
输入:
["PSH-1","PSH2","MIN","TOP","POP","PSH1","TOP","MIN"]
返回值:
-1,2,1,-1
2、代码实现
C++
#include <stack>
class Solution {
public:
void push(int value) {
st.push(value);
if (stmin.empty() || value <= stmin.top()) {
stmin.push(value);
}
}
void pop() {
int value = st.top();
st.pop();
if (!stmin.empty() && value == stmin.top()) {
stmin.pop();
}
}
int top() {
return st.top();
}
int min() {
return stmin.top();
}
private:
stack<int> st;
stack<int> stmin;
};
Q79、好串
1、题目描述
描述
牛牛喜欢跟字符串玩耍,他学会了一种新操作:在当前字符串中任意位置(包括开头和结尾)插入子串 “ab”。
牛牛称一个字符串为好串,当且仅当它可以通过若干次上述操作从空串生成。
例如,ab
、aabb
、aababb
都是好串,而 aab
、ba
、abbb
不是好串。
现给定一个字符串 s
,请判断 s
是否是好串。
输入描述:
在一行中输入一个字符串 s
,仅由小写字母组成,长度满足 1 ≦ ∣s∣ ≦ 105。
输出描述:
如果 s
是好串,输出 Good
;否则输出 Bad
。
示例1
输入:
ab
输出:
Good
说明:
初始空串,插入一次 "ab" 即可得到 "ab"。
示例2
输入:
aab
输出:
Bad
说明:
无法通过插入 "ab" 操作得到 "aab"。
示例3
输入:
abaababababbaabbaaaabaababaabbabaaabbbbbbbb
输出:
Bad
2、解题思路
关键观察:
- 字符顺序:插入
"ab"
时,总是插入'a'
和'b'
这对字符,所以最终字符串中'a'
和'b'
的数量必须相等。 - 前缀条件:在任何时刻,
'a'
的数量必须不少于'b'
的数量,否则无法通过插入"ab"
生成这样的字符串(因为每次插入都是先'a'
后'b'
)。 - 构建过程:可以通过逆向思维,从字符串
s
逐步删除"ab"
子串,如果能删到空字符串,则s
是好串。
具体步骤:
- 检查字符串
s
中'a'
和'b'
的数量是否相等。如果不相等,直接返回Bad
。 - 使用栈来模拟删除
"ab"
的过程:- 遍历字符串
s
,遇到'a'
就入栈。 - 遇到
'b'
时,检查栈顶是否为'a'
:- 如果是,弹出
'a'
,表示删除一个"ab"
子串。 - 如果不是(栈为空或栈顶不是
'a'
),则无法删除,返回Bad
。
- 如果是,弹出
- 遍历字符串
- 遍历结束后,如果栈为空,则所有
"ab"
子串被成功删除,返回Good
;否则返回Bad
。
3、代码实现
C++
#include <iostream>
#include <string>
using namespace std;
string is_good_string(const string& s) {
int a_count = 0, b_count = 0;
for (char c : s) {
if (c == 'a') {
a_count++;
} else if (c == 'b') {
b_count++;
}
if (b_count > a_count) {
return "Bad";
}
}
return b_count == a_count ? "Good" : "Bad";
}
int main() {
string s;
cin >> s;
cout << is_good_string(s) << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:O(n),其中
n
是字符串s
的长度。遍历字符串两次(统计字符和栈操作)。 - 空间复杂度:O(n),栈在最坏情况下需要存储
n/2
个'a'
。
Q80、吐泡泡
1、题目描述
描述
小鱼儿会吐出两种泡泡:大泡泡 “O”,小泡泡 “o”;两种泡泡的变化规则如下:
- 任意两个相邻的小泡泡会融合成一个大泡泡;
- 任意两个相邻的大泡泡会相互爆炸,变成空白(即消失)。
上述合并与爆炸过程自左至右依次进行,直至无法再进行任何操作。
例如,对于初始泡泡序列 “ooOOoooO”,经过一段时间后会变成 “oO”。
输入描述:
第一行输入一个整数 T (1 ≦ T ≦ 10) 代表数据组数。
接下来 T 行,每行一个仅由 ‘O’ 和 ‘o’ 构成的字符串 ss,字符串长度不超过 105。
输出描述:
每组输出仅包含一行,输出一行字符串代表小鱼儿吐出的泡泡经过融合以后所剩余的泡泡。
示例1
输入:
1 ooOOoooO
输出:
oO
说明:
示例2
输入:
1 OOOOOOOOOOOOOOOooooooooooooooooooOOoOoOoOOOoOoOoOOoOooOoOOoOoOoOoOoOoOoOoOoOooOoOoOOoooOOOOoOOoooOOoOOOOOooOoOOOoOOoooOoOOOooOooooOoOooOoOooOoOooOoOOOOOOOOOOOOOOoOoOoOooOOoOooOoOOoOoOOOOooooOOOOOooooooOOOOOOoooooOoOooOoOoOoooOoOOOOoOoOoOOOOOOOOOOoOooOoOooOOoOOoOooOooOOoooOOOoOoOooOOooOoOOOoOOoOOOoOooOoOOOooOOoooOOoOOoOooOOOOoOooOoOoOoOooOoOoO
输出:
oOoOoOoOoOoO
说明:
2、解题思路
关键观察:
- 泡泡的变化是自左至右依次进行的,因此每次变化会影响后续的扫描。
- 每次变化后,新的泡泡可能会触发更多的变化(例如融合后产生的大泡泡可能与相邻的大泡泡爆炸)。
- 这类似于括号匹配问题,可以用栈来处理。
具体步骤:
- 使用栈来模拟泡泡的变化过程:
- 遍历字符串
s
,依次将泡泡压入栈。 - 每次压入后,检查栈顶的两个泡泡是否满足变化条件:
- 栈顶两个
'o'
融合为'O'
(弹出两个'o'
,压入'O'
)。 - 栈顶两个
'O'
爆炸(弹出两个'O'
)。
- 栈顶两个
- 重复检查直到栈顶不再满足变化条件。
- 遍历字符串
- 栈中剩余的泡泡即为最终结果。
3、代码实现
C++
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
while (n--) {
string s;
cin >> s;
string ret;
for (char c : s) {
if (ret.empty() || ret.back() != c) {
ret.push_back(c);
} else if (c == 'o') {
ret.pop_back();
if (ret.back() == 'O') {
ret.pop_back();
} else {
ret.push_back('O');
}
} else if (c == 'O') {
ret.pop_back();
}
}
cout << ret << endl;
}
return 0;
}
4、复杂度分析
- 时间复杂度:O(n),其中
n
是字符串s
的长度。每个泡泡最多入栈和出栈一次。 - 空间复杂度:O(n),栈在最坏情况下存储所有泡泡。
Q81、有效括号序列
1、题目描述
描述
给出一个仅包含字符 ‘(’,’)’, ‘{’,’}’, ‘[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()" 和 “()[]{}” 都是合法的括号序列,但 “(]” 和 “([)]” 不合法。
数据范围:字符串长度 0 ≤ n ≤ 10000
要求:空间复杂度 O(n),时间复杂度 O(n)
示例1
输入:
"["
返回值:
false
示例2
输入:
"[]"
返回值:
true
2、代码实现
C++
#include <stack>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return bool布尔型
*/
bool isValid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else {
if (st.empty() || (c == ')' && st.top() != '(') || (c == ']' &&
st.top() != '[') || (c == '}' && st.top() != '{')) {
return false;
}
st.pop();
}
}
return st.empty();
}
};
Q82、表达式求值
1、题目描述
描述
请写一个整数计算器,支持加减乘三种运算和括号。
数据范围:0 ≤ ∣s∣ ≤ 100,保证计算结果始终在整型范围内
要求:空间复杂度: O(n),时间复杂度 O(n)
示例1
输入:
"1+2"
返回值:
3
示例2
输入:
"(2*(3-4))*5"
返回值:
-10
示例3
输入:
"3+2*3*4-1"
返回值:
26
2、解题思路
关键观察:
- 运算符优先级:括号
()
的优先级最高,其次是乘法*
,最后是加减法+
和-
。 - 运算顺序:同一优先级的运算符从左到右计算(加减法)或直接计算(乘法)。
- 括号处理:括号内的表达式需要优先计算,可以用递归或栈来处理。
方法选择:
- 双栈法:使用两个栈,一个存储数字(
nums
),一个存储运算符(ops
),按照优先级顺序进行计算。 - 递归下降法:通过递归的方式处理括号内的表达式,适合处理嵌套括号。
这里选择双栈法,因为它更直观且时间复杂度为 O(n)。
具体步骤:
- 初始化两个栈:
nums
(数字)和ops
(运算符)。 - 遍历字符串
s
:- 遇到数字:提取完整的数字并压入
nums
。 - 遇到
(
:压入ops
。 - 遇到
)
:计算ops
栈顶到(
的所有运算符。 - 遇到运算符
+
、-
、*
:- 若当前运算符优先级 ≤
ops
栈顶运算符优先级,则先计算栈顶运算符。 - 压入当前运算符。
- 若当前运算符优先级 ≤
- 遇到数字:提取完整的数字并压入
- 遍历结束后,计算
ops
剩余的所有运算符。 nums
栈顶即为最终结果。
3、代码实现
C++
#include <cctype>
#include <stack>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 返回表达式的值
* @param s string字符串 待计算的表达式
* @return int整型
*/
int solve(string s) {
stack<int> nums;
stack<char> ops;
int n = s.size();
for (int i = 0; i < n; ++i) {
char c = s[i];
if (isdigit(c)) {
int num = 0;
while (i < n && isdigit(s[i])) {
num = num * 10 + (s[i] - '0');
++i;
}
--i;
nums.push(num);
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
while (ops.top() != '(') {
calculate(nums, ops);
}
ops.pop(); // 弹出 '('
} else {
// 处理运算符
while (!ops.empty() && ops.top() != '(' &&
precedence(ops.top()) >= precedence(c)) {
calculate(nums, ops);
}
ops.push(c);
}
}
while (!ops.empty()) {
calculate(nums, ops);
}
return nums.top();
}
// 定义运算符的优先级
int precedence(char op) {
if (op == '+' || op == '-') {
return 1;
}
if (op == '*' || op == '/') {
return 2;
}
return 0;
}
// 计算两个数的结果
void calculate(stack<int>& nums, stack<char>& ops) {
int b = nums.top();
nums.pop();
int a = nums.top();
nums.pop();
char op = ops.top();
ops.pop();
if (op == '+') {
nums.push(a + b);
} else if (op == '-') {
nums.push(a - b);
} else if (op == '*') {
nums.push(a * b);
}
}
};
4、复杂度分析
- 时间复杂度:O(n),每个字符最多入栈和出栈一次。
- 空间复杂度:O(n),两个栈的空间最坏为 O(n)。
Q83、牛牛与后缀表达式
1、题目描述
描述
给定牛牛一个后缀表达式 s,计算它的结果,例如,1+1对应的后缀表达式为1#1#+,‘#’作为操作数的结束符号。
其中,表达式中只含有‘+’、’-‘、’*‘三种运算,不包含除法。
本题保证表达式一定合法,且计算过程和计算结果的绝对值一定不会超过1018
示例1
输入:
"1#1#+"
返回值:
2
说明:
1#1#+这个后缀表达式表示的式子是1+1,结果为2
示例2
输入:
"12#3#+15#*"
返回值:
225
说明:
12#3#+15#*这个后缀表达式表示的式子是(12+3)*15,结果为225
备注:
1 ≤ 表达式中操作数 ≤ 109
1 ≤ 表达式长度 ≤ 106
2、解题思路
关键观察:
- 后缀表达式计算规则:
- 遇到操作数时,将其压入栈。
- 遇到运算符时,弹出栈顶的两个操作数,进行计算后将结果压入栈。
- 最终栈顶即为表达式结果。
- 操作数提取:
- 由于操作数由
'#'
分隔,需要逐个字符读取直到'#'
,拼接成完整的数字。
- 由于操作数由
具体步骤:
- 初始化一个栈
nums
用于存储操作数。 - 遍历字符串
s
:- 如果是数字,提取完整的数字(直到
'#'
)并压入nums
。 - 如果是运算符,弹出栈顶的两个操作数,计算后将结果压入栈。
- 如果是数字,提取完整的数字(直到
- 最终
nums
栈顶即为结果。
3、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给定一个后缀表达式,返回它的结果
* @param str string字符串
* @return long长整型
*/
long long legalExp(string str) {
stack<long long> nums;
int n = str.size();
for (int i = 0; i < n; ++i) {
char c = str[i];
if (isdigit(c)) {
long long num = 0;
while (i < n && isdigit(str[i])) {
num = num * 10 + (str[i] - '0');
++i;
}
nums.push(num);
} else if (c == '+' || c == '-' || c == '*') {
long long b = nums.top();
nums.pop();
long long a = nums.top();
nums.pop();
if (c == '+') {
nums.push(a + b);
} else if (c == '-') {
nums.push(a - b);
} else if (c == '*') {
nums.push(a * b);
}
}
}
return nums.top();
}
};
4、复杂度分析
- 时间复杂度:O(n),每个字符最多处理一次。
- 空间复杂度:O(n),栈的最坏情况存储所有操作数。
Q84、验证栈序列
1、题目描述
描述
给定长度为 n 的入栈序列 pushed 和长度为 n 的出栈序列 popped,两者均为 1∼n 的排列。初始时栈为空,只允许在栈顶进行插入(入栈)和删除(出栈)操作;若可通过若干操作使出栈顺序等于 popped,则称 popped 为合法出栈序列。
现有 q 组测试,每组给定对应序列,判断 popped 是否为合法出栈序列。
【名词解释】
【排列】长度为 n 的 排列 是由 1∼n 的 n 个整数按任意顺序组成的序列,其中每个整数恰好出现一次。
输入描述:
第一行输入整数 q (1 ≦ q ≦ 5),表示测试组数。
接下来对于每组测试,依次输入:
- 一行整数 n (1 ≦ n ≦ 105),表示序列长度;
- 一行 n 个整数,为入栈序列 pushed1, … , pushedn;
- 一行 n 个整数,为出栈序列 popped1, …, poppedn。
输出描述:
对于每组测试,输出一行,如果 popped 为合法出栈序列,则输出 Yes;否则输出 No。
示例1
输入:
2 5 1 2 3 4 5 2 5 4 1 3 5 1 2 3 4 5 2 5 4 3 1
输出:
No Yes
示例2
输入:
2 3 1 2 3 2 3 1 3 1 2 3 2 1 3
输出:
Yes Yes
2、解题思路
关键观察:
- 栈的操作规则:
- 只能从栈顶出栈。
- 出栈顺序必须与
popped
的顺序一致。
- 模拟过程:
- 遍历
pushed
序列,逐个入栈。 - 每次入栈后,检查栈顶是否与
popped
的当前元素相同:- 如果相同,则出栈,并移动到
popped
的下一个元素。 - 继续检查栈顶,直到不匹配为止。
- 如果相同,则出栈,并移动到
- 最终,如果栈为空且
popped
的所有元素都匹配,则为合法序列。
- 遍历
具体步骤:
-
初始化一个栈
st
和一个指针i
指向popped
的第一个元素。 -
遍历
pushed
序列:- 将当前元素入栈。
- 循环检查栈顶是否等于
popped[i]
:- 如果相等,出栈并
i++
。
- 如果相等,出栈并
-
遍历结束后,检查栈是否为空:
- 如果为空,则
popped
是合法序列(Yes
)。 - 否则,不是合法序列(
No
)。
- 如果为空,则
3、代码实现
C++
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
bool valid(const vector<int>& pushed, const vector<int>& popped) {
stack<int> s;
int i = 0;
for (int num : pushed) {
s.push(num);
while (!s.empty() && s.top() == popped[i]) {
s.pop();
++i;
}
}
return s.empty();
}
int main() {
int size;
cin >> size;
while (size--) {
int n;
cin >> n;
vector<int> pushed(n);
for (int i = 0; i < n; ++i) {
cin >> pushed[i];
}
vector<int> popped(n);
for (int i = 0; i < n; ++i) {
cin >> popped[i];
}
if (valid(pushed, popped)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:O(n),每个元素最多入栈和出栈一次。
- 空间复杂度:O(n),栈的最坏情况存储所有元素。
Q85、栈和排序
1、题目描述
描述
给定一个从 1 到 n 的排列 P,以及一个空栈。你按顺序将排列中的元素依次入栈,可以在任意时刻选择将栈顶元素出栈并将其加入输出序列。入栈顺序不可改变。
理想情况下,你想得到一个严格从大到小排序的输出序列 n, n−1, …, 1,但受栈操作限制可能无法实现。当无法完全排序时,请输出字典序最大的合法出栈序列。
输入描述:
在一行中输入一个整数 n (1 ≦ n ≦ 106)。
第二行输入 n 个整数,表示排列 P 中的元素,用空格分隔。保证给出的是一个从 1 到 n 的排列。
输出描述:
输出一行,包含若干整数,表示最终的出栈序列,用空格分隔,结尾不输出多余空格。
示例1
输入:
5 2 1 5 3 4
输出:
5 4 3 1 2
说明:
入栈顺序和操作示例如下: 2 入栈; 1 入栈; 5 入栈; 5 出栈; 3 入栈; 4 入栈; 4 出栈; 3 出栈; 1 出栈; 2 出栈。
2、解题思路
关键观察:
- 字典序最大的序列:在每一步选择出栈时,尽可能出栈当前最大的元素。
- 栈的性质:只能从栈顶出栈,出栈顺序受入栈顺序限制。
具体步骤:
- 初始化:使用一个栈
st
和一个指针target
指向当前期望的最大值n
。 - 遍历
P
序列:- 将当前元素
P[i]
入栈。 - 检查栈顶元素是否等于
target
:- 如果相等,出栈并加入输出序列,同时
target--
。 - 继续检查栈顶,直到不匹配为止。
- 如果相等,出栈并加入输出序列,同时
- 将当前元素
- 处理剩余元素:将栈中剩余元素依次出栈并加入输出序列。
优化:
- 使用
std::vector
存储结果,避免频繁的字符串拼接。 - 直接比较栈顶元素与
target
,确保字典序最大。
3、代码实现
C++
#include <iostream>
#include <stack>
using namespace std;
int main() {
int n, num;
cin >> n;
int i = n;
stack<int> s;
while (n--) {
cin >> num;
s.push(num);
while (!s.empty() && s.top() == i) {
cout << s.top() << " ";
s.pop();
i--;
}
}
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
i--;
}
return 0;
}
4、复杂度分析
- 时间复杂度:O(n),每个元素最多入栈和出栈一次。
- 空间复杂度:O(n),栈和结果列表最坏情况存储所有元素。
13、队列
Q86、【模板】队列操作
1、题目描述
描述
给定一个空队列,依次执行 n 个操作,操作类型定义如下:
1 x
:将整数 x (−109 ≦ x ≦ 109) 入队;2
:若队列非空,则仅将队头元素出队,否则输出ERR_CANNOT_POP
;3
:查询并输出队首元素,队列为空时输出ERR_CANNOT_QUERY
;4
:输出队列当前元素数量。
输入描述:
第一行包含整数 n (1 ≦ n ≦ 105),表示操作总数。
接下来 n 行,每行描述一个操作,格式如上所述。
输出描述:
对于每个操作 2
、3
、4
,按执行顺序,每行输出对应的结果。
示例1
输入:
7 1 10 1 20 3 4 2 3 2
输出:
10 2 20
说明:
在样例中: ∙ 执行 `1 10` 和 `1 20` 后队列为 [10,20]; ∙ `3` 输出队首 10; ∙ `4` 输出队列大小 2; ∙ `2` 出队并输出 10;队列变为 [20]; ∙ `3` 输出队首 20; ∙ `2` 出队并输出 20;此后队列为空。
2、解题思路
-
选择数据结构:
- 使用
std::queue
或std::deque
模拟队列操作。 std::queue
是一个适配器,底层默认使用std::deque
,适合先进先出(FIFO)的操作。
- 使用
-
处理操作:
- 遍历每个操作:
- 操作
1 x
:直接push
到队列。 - 操作
2
:检查队列是否为空,非空则pop
并输出队首;否则输出-1
。 - 操作
3
:检查队列是否为空,非空则输出front
;否则输出-1
。 - 操作
4
:输出队列的size
。
- 操作
- 遍历每个操作:
-
注意事项:
- 输入规模较大(
n ≤ 1e5
),使用高效的队列实现。 - 输出结果需严格按照操作顺序。
- 输入规模较大(
3、代码实现
C++
#include <iostream>
#include <queue>
using namespace std;
int main() {
int n, code, x;
cin >> n;
queue<int> q;
while (n--) {
cin >> code;
if (code == 1) {
cin >> x;
q.push(x);
} else if (code == 2) {
if (q.empty()) {
cout << "ERR_CANNOT_POP" << endl;
} else {
q.pop();
}
} else if (code == 3) {
if (q.empty()) {
cout << "ERR_CANNOT_QUERY" << endl;
} else {
cout << q.front() << endl;
}
} else if (code == 4) {
cout << q.size() << endl;
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:每个操作均为
O(1)
,总复杂度O(n)
。 - 空间复杂度:队列存储最多
n
个元素,O(n)
。
Q87、无法吃午餐的学生数量
1、题目描述
描述
定义长度为 n (1 ≦ n ≦ 100) 的序列 students = [s1, …, sn] (si ∈ {0, 1}) 表示队列中第 i 名学生的偏好,序列 sandwiches = [t1, …, tn] (ti ∈ {0, 1}) 表示栈顶至栈底的三明治类型(t1 为栈顶)。初始时,队列与栈均包含 n 名学生与 n 个三明治。每步操作如下:
- 若 s1 = t1,则该学生取走该三明治并移出队列,三明治出栈;
- 否则,将队首学生移至队尾;
重复上述操作直至所有剩余学生均不满足栈顶三明治偏好。
你需要补全一个函数求无法拿到三明治的学生人数,接受的参数为:
∙ ∙整数序列 students,长度为 n;
∙ ∙整数序列 sandwiches,长度为 n。
函数的返回值为一个正整数,表示无法拿到三明治的学生人数。
示例1
输入:
[1,1,0,1],[1,0,0,1]
返回值:
2
2、解题思路
-
模拟过程:
- 使用队列来模拟学生的排队顺序。
- 使用栈来模拟三明治的发放顺序(栈顶先发)。
- 循环执行匹配操作,直到无法继续匹配。
-
终止条件:
- 当队列中没有一个学生能匹配当前栈顶三明治时,停止循环。
- 此时队列中剩余的学生即为无法拿到三明治的学生人数。
-
优化:
- 在每次循环中,如果队首学生和栈顶三明治匹配,直接出队和出栈。
- 如果不匹配,将队首学生移至队尾。
- 需要记录当前队列的长度,避免无限循环(比如所有学生都不匹配栈顶三明治)。
3、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param students int整型vector
* @param sandwiches int整型vector
* @return int整型
*/
int countStudents(vector<int>& students, vector<int>& sandwiches) {
queue<int> studentQueue;
for (int student : students) {
studentQueue.push(student);
}
int sandwichIndex = 0;
int n = sandwiches.size();
int unmatchedCount = 0;
while (!studentQueue.empty() && unmatchedCount < students.size()) {
if (studentQueue.front() == sandwiches[sandwichIndex]) {
studentQueue.pop();
sandwichIndex++;
unmatchedCount = 0;
} else {
studentQueue.push(studentQueue.front());
studentQueue.pop();
unmatchedCount++;
}
}
return studentQueue.size();
}
};
4、复杂度分析
- 时间复杂度:最坏情况下,每个学生被移动到队尾
O(n)
次,总复杂度O(n^2)
。但实际中因为学生类型有限(0 或 1),通常不会达到最坏情况。 - 空间复杂度:
O(n)
,用于存储队列和栈。
Q88、队列消数
1、题目描述
描述
给定正整数序列 a = (a1, a2, …, an) (1 ≦ ai ≦ 100) 及索引 k (0 ≦ k < n ≦ 100),定义初始队列为元素下标序列 (1, 2, …, n)。
重复以下过程直至索引 kk 对应元素被移除:
- 取出队首下标 i,耗时 1 秒;
- 若 ai > 1,令 ai ← ai − 1 并将 i 加入队尾;
- 否则,将其从队列中移除。
返回目标元素被移除时的总耗时。
示例1
输入:
[1,1,4,5,1,4],2
返回值:
13
说明:
2、解题思路
-
模拟队列操作:
- 使用队列存储当前需要处理的下标。
- 每次取出队首下标
i
,耗时1
秒。 - 如果
a[i] > 1
,则a[i]
减1
,并将i
重新加入队尾。 - 如果
a[i] == 1
,则移除i
,并检查是否为k
,如果是则返回总耗时。
-
终止条件:
- 当
k
对应的元素被移除时,停止模拟,返回总耗时。
- 当
3、代码实现
C++
#include <queue>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param tickets int整型vector
* @param k int整型
* @return int整型
*/
int timeRequiredToBuy(vector<int>& tickets, int k) {
queue<int> q;
for (int i = 0; i < tickets.size(); ++i) {
q.push(i);
}
int time = 0;
while (!q.empty()) {
int cur = q.front();
q.pop();
time++;
if (tickets[cur] > 1) {
tickets[cur]--;
q.push(cur);
} else {
if (cur == k) {
return time;
}
}
}
return -1;
}
};
4、复杂度分析
- 时间复杂度:
O(n * max(a_i))
,最坏情况下每个元素会被处理a_i
次。 - 空间复杂度:
O(n)
,用于存储队列。
Q89、用两个栈实现队列
1、题目描述
描述
用两个栈来实现一个队列,使用 n 个元素来完成 n 次在队列尾部插入整数 (push) 和 n 次在队列头部删除整数 (pop) 的功能。 队列中的元素为 int 类型。保证操作合法,即保证 pop 操作时队列内已有元素。
数据范围: n ≤ 1000
要求:存储 n 个元素的空间复杂度为 O(n) ,插入与删除的时间复杂度都是 O(1)
示例1
输入:
["PSH1","PSH2","POP","POP"]
返回值:
1,2
说明:
"PSH1":代表将1插入队列尾部 "PSH2":代表将2插入队列尾部 "POP“:代表删除一个元素,先进先出=>返回1 "POP“:代表删除一个元素,先进先出=>返回2
示例2
输入:
["PSH2","POP","PSH1","POP"]
返回值:
2,1
2、代码实现
C++
class Solution {
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
}
int ret = stack2.top();
stack2.pop();
return ret;
}
private:
stack<int> stack1;
stack<int> stack2;
}
Q90、参议院投票
1、题目描述
描述
给定长度为 n 的字符串 s,其字符 si ∈ {R, D} 表示第 i 位参议员的阵营。其中 R 代表红帮,D 代表黑帮。
参议员按照索引 1 到 n 的顺序循环行动。若剩余有行动权的参议员均属于同一阵营,则该阵营获胜并结束流程;否则,当前参议员可选择:
- 弹劾一名仍可行动的参议员,使其在后续轮次中失去行动权。
- (仅当剩余参议员同阵营时)直接宣布胜利。
你需要实现一个函数,求出所有参议员均采用最优策略时,最终获胜的阵营的名称。
函数接受的参数为一个字符串 s,长度 n (1 ≦ n ≦ 104),且 si ∈ {R, D}。
函数的返回值为一个字符串 Red
或 Dark
,分别表示红帮或黑帮获胜。
示例1
输入:
"DR"
返回值:
"Dark"
说明:
第 1 轮时,第一个参议员来自 Dark 阵营,他可以使用第一项权利禁止第二个参议员的权利 这样第二个参议员就无法使用任何权利了。 第 2 轮时,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人。
示例2
输入:
"DRR"
返回值:
"Red"
说明:
第 1 轮时,第一个来自 Dark 阵营的参议员可以使用第一项权利禁止第二个参议员的权利 第 2 轮时,第三个来自 Red 阵营的参议员可以使用他的第一项权利禁止第一个参议员的权利 这样在第 3 轮只剩下第三个参议员拥有投票的权利,于是他可以宣布胜利。
2、解题思路
-
模拟投票过程:
- 使用两个队列分别存储 R 和 D 阵营的参议员索引。
- 每次从队列头部取出一个参议员,弹劾对方阵营的一个参议员(从对方队列头部取出)。
- 被弹劾的参议员失去行动权,未被弹劾的参议员重新加入队列尾部(因为下一轮会再次行动)。
- 循环直到某一阵营的队列为空。
-
最优策略:
- 每个参议员都会优先弹劾对方阵营中最早行动的参议员(即队列头部),因为这样可以尽快削弱对方阵营。
3、代码实现
C++
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 求出最终获胜帮派的名称
* @param s string字符串
* @return string字符串
*/
string predictVictory(string s) {
queue<char> red, dark;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (s[i] == 'R') {
red.push(i);
} else {
dark.push(i);
}
}
while (!red.empty() && !dark.empty()) {
int r = red.front();
red.pop();
int d = dark.front();
dark.pop();
if (r < d) {
red.push(r + n);
} else {
dark.push(d + n);
}
}
return red.empty() ? "Dark" : "Red";
}
};
4、复杂度分析
- 时间复杂度:
O(n)
,每个参议员最多被处理两次(一次弹劾,一次重新加入)。 - 空间复杂度:
O(n)
,用于存储两个队列。
Q91、机器翻译
1、题目描述
描述
牛牛的电脑上安装了一个机器翻译软件,它依次将每个英文单词替换为对应的中文含义。软件内部有 M 个缓存单元,每个单元存放一个单词和译义。翻译某个单词时:
- 如果缓存中已有该单词,则直接使用(缓存命中);
- 否则需要到外存词典查找(缓存未命中),并将该单词及译义插入缓存:若缓存未满,则占用一个空闲单元;若缓存已满,则清除最早进入缓存的单词后插入新单词。
给定长度为 N 的文章(由 N 个整数编码表示单词),初始缓存为空,统计翻译过程中需要查词典的次数。
输入描述:
第一行输入两个整数 M,N(1 ≦ M ≦ 100,1 ≦ N ≦ 1000),分别表示缓存容量和文章单词数。
第二行输入 N 个整数 w1, w2, …, wN(0 ≦ wi ≦ 1000),表示文章中按顺序出现的单词编码。
输出描述:
输出一个整数,表示翻译过程中缓存未命中(查词典)的总次数。
示例1
输入:
3 7 1 2 1 5 4 4 1
输出:
5
说明:
翻译过程示例(缓存状态记录自左向右为最早到最近): 初始:空 1: miss,缓存->[1] 2: miss,缓存->[1,2] 1: hit,缓存->[1,2] 5: miss,缓存->[1,2,5] 4: miss,缓存->[2,5,4](替换1) 4: hit,缓存->[2,5,4] 1: miss,缓存->[5,4,1](替换2) 共 miss 5 次。
2、解题思路
-
模拟缓存操作:
- 使用一个队列(或列表)来模拟缓存,队列头部是最早加入的单词,尾部是最新加入的单词。
- 遍历文章中的每个单词:
- 如果单词在缓存中,跳过(缓存命中)。
- 如果不在缓存中(缓存未命中),计数器加 1:
- 如果缓存未满,直接加入队列尾部。
- 如果缓存已满,移除队列头部,加入队列尾部。
-
数据结构选择:
- 使用
unordered_set
快速判断单词是否在缓存中。 - 使用
queue
或deque
记录缓存中单词的顺序。
- 使用
3、代码实现
C++
#include <iostream>
#include <queue>
#include <unordered_set>
using namespace std;
int main() {
int n, size, code;
cin >> n >> size;
queue<int> cacheQueue;
unordered_set<int> cacheSet;
int misses = 0;
while (size--) {
cin >> code;
if (cacheSet.find(code) == cacheSet.end()) {
misses++;
if (cacheQueue.size() >= n) {
cacheSet.erase(cacheQueue.front());
cacheQueue.pop();
}
cacheQueue.push(code);
cacheSet.insert(code);
}
}
cout << misses << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:
O(N)
,每个单词处理时间为O(1)
(unordered_set
和queue
操作均为O(1)
)。 - 空间复杂度:
O(M)
,缓存最多存储M
个单词。
14、集合
Q92、【模板】集合操作
1、题目描述
描述
您需要动态地维护一个初始为空的集合 M,并且支持以下操作:
- 向集合 M 中插入一个数 x,如果 x 在集合 M 中已经存在,则忽略本次操作。
- 从集合 M 中删除一个数 x,如果 x 不在集合 M 中,则忽略本次操作。
- 查询一个数 x 是否在集合 M 中。
- 查询集合 M 中元素的个数,即集合 M 的大小。
- 查询集合 M 中 x 的前驱(前驱定义为小于 x 且最大的数),如果前驱不存在,则输出 −1。
- 查询集合 M 中 x 的后继(后继定义为大于 x 且最小的数),如果后继不存在,则输出 −1。
输入描述:
第一行输入一个整数 n(1 ≦ n ≦ 105),表示操作的个数。
接下来 n 行,每行输入两个整数 opt 和 x(1 ≦ opt ≦ 6,0 ≦ x ≦ 106),分别表示操作类型和操作数。
输出描述:
对于操作类型为 3、4、5、6 的每次操作,输出一个整数,表示对应的查询结果,每个结果占一行。
示例1
输入:
2 1 4 4
输出:
1
说明:
第一步插入操作向集合 M 中插入了元素 4;第二步查询操作类型为 4,查询集合大小,当前集合中只有元素 4,大小为 1,因此输出 1。
示例2
输入:
5 1 5 1 10 1 20 5 15 6 15
输出:
10 20
说明:
前三步插入操作使集合 M={5,10,20};第四步操作类型为 5,查询前驱,数字 15 在集合中前驱为 10,输出 10;第五步操作类型为 6,查询后继,数字 15 在集合中后继为 20,输出 20。
2、解题思路
-
数据结构选择:
- 需要高效支持插入、删除、查找前驱和后继操作。
std::set
或std::unordered_set
可以高效处理插入、删除和存在性检查。- 但
std::unordered_set
不支持有序操作(前驱和后继),因此选择std::set
(基于红黑树实现,有序且操作高效)。
-
前驱和后继查询:
std::set
提供lower_bound
和upper_bound
方法,可以高效找到前驱和后继。
-
具体操作:
- 插入:使用
insert
方法,自动去重。 - 删除:使用
erase
方法,若元素不存在则忽略。 - 存在性检查:使用
find
方法。 - 前驱:使用
lower_bound
找到第一个不小于x
的迭代器,前一个元素即为前驱。 - 后继:使用
upper_bound
找到第一个大于x
的迭代器。
- 插入:使用
3、代码实现
C++
#include<bits/stdc++.h>
#include <set>
using namespace std;
set<int> s;
void insertValue(int x) {
//TODO 实现插入逻辑
s.insert(x);
}
void eraseValue(int x) {
//TODO 实现删除逻辑
if (s.count(x)) {
s.erase(x);
}
}
int xInSet(int x) {
//TODO 实现存在性检查
if (s.count(x)) {
return 1;
}
return 0;
}
int sizeOfSet() {
//TODO 返回集合大小
return s.size();
}
int getPre(int x) {
//TODO 实现找前驱
auto it = s.lower_bound(x);
if (it == s.begin()) {
return -1;
}
--it;
return *it;
}
int getBack(int x) {
//TODO 实现找后继
auto it = s.upper_bound(x);
if (it != s.end()) {
return *it;
}
return -1;
}
int main() {
int q, op, x;
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x;
insertValue(x);
}
if (op == 2) {
cin >> x;
eraseValue(x);
}
if (op == 3) {
cin >> x;
if (xInSet(x)) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
if (op == 4) {
cout << sizeOfSet() << endl;
}
if (op == 5) {
cin >> x;
cout << getPre(x) << endl;
}
if (op == 6) {
cin >> x;
cout << getBack(x) << endl;
}
}
return 0;
}
4、复杂度分析
- 插入、删除、存在性检查:
O(log N)
,set
基于红黑树实现。 - 前驱、后继查询:
O(log N)
,利用lower_bound
和upper_bound
。 - 空间复杂度:
O(N)
,存储所有元素。
Q93、【模板】多重集合操作
1、题目描述
描述
维护一个初始为空的多重集合 M,支持如下六种操作:
- 操作 1:向集合 M 插入一个整数 x。
- 操作 2:从集合 M 删除一个整数 x,若 x 不存在,则忽略;若存在多个,则只删除一个。
- 操作 3:查询整数 x 在集合 M 中的个数。
- 操作 4:查询集合 M 中元素的总个数(含重复计数)。
- 操作 5:查询整数 x 在集合 M 中的前驱(定义为小于 x 的最大元素),若不存在则返回 −1。
- 操作 6:查询整数 x 在集合 M 中的后继(定义为大于 x 的最小元素),若不存在则返回 −1。
输入描述:
第一行输入一个整数 n(1 ≦ n ≦ 105),表示操作总数。
接下来 n 行,每行输入两个整数 opt 和 x(0 ≦ ∣x∣ ≦ 106 ),opt 表示操作类型,1 ≦ opt ≦ 6。
输出描述:
对于每个操作 3、4、5、6,在一行中输出一个整数,表示对应操作的查询结果。
示例1
输入:
2 1 1 3 1
输出:
1
说明:
首先插入 1,然后查询 1 在集合中的个数为 1。
示例2
输入:
6 1 2 1 2 3 2 2 2 3 2 4 0
输出:
2 1 1
说明:
在两次插入 2 后,查询个数为 2;删除一次后,查询个数为 1;最后查询集合大小,当前集合中仅有一个 2,故输出 1。
2、解题思路
我们需要一个数据结构来高效支持插入、删除、计数和查找前驱后继的操作。由于集合允许重复元素,我们可以使用 std::multiset
,它基于红黑树实现,插入、删除和查找操作的时间复杂度均为 O(log N)。
- 插入操作:直接调用
multiset.insert(x)
。 - 删除操作:使用
multiset.erase(multiset.find(x))
删除一个 x(若存在)。 - 计数操作:
multiset.count(x)
可以高效统计 x 的个数。 - 总元素个数:
multiset.size()
返回所有元素的数量(含重复)。 - 前驱查询:使用
lower_bound(x)
找到第一个不小于 x 的元素,前一个元素即为前驱。 - 后继查询:使用
upper_bound(x)
找到第一个大于 x 的元素。
3、代码实现
C++
#include<bits/stdc++.h>
#include <map>
#include <set>
using namespace std;
multiset<int> m;
void insertValue(int x) {
//TODO 实现插入逻辑
m.insert(x);
}
void eraseValue(int x) {
//TODO 实现删除逻辑
auto it = m.find(x);
if (it != m.end()) {
m.erase(it);
}
}
int xCount(int x) {
//TODO 求x在集合中的个数
return m.count(x);
}
int sizeOfSet() {
//TODO 返回集合大小
return m.size();
}
int getPre(int x) {
//TODO 实现找前驱
auto it = m.lower_bound(x);
if (it == m.begin()) {
return -1;
}
return *(--it);
}
int getBack(int x) {
//TODO 实现找后继
auto it = m.upper_bound(x);
if (it == m.end()) {
return -1;
} else {
return *it;
}
}
int main() {
int q, op, x;
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x;
insertValue(x);
}
if (op == 2) {
cin >> x;
eraseValue(x);
}
if (op == 3) {
cin >> x;
cout << xCount(x) << endl;
}
if (op == 4) {
cout << sizeOfSet() << endl;
}
if (op == 5) {
cin >> x;
cout << getPre(x) << endl;
}
if (op == 6) {
cin >> x;
cout << getBack(x) << endl;
}
}
return 0;
}
4、复杂度分析
- 插入、删除、查找:O(log N)。
- 计数:O(log N + count),因为
count
需要遍历所有相同元素。 - 前驱、后继查询:O(log N)。
- 总元素个数:O(1)。
Q94、动态整数集最近值提取
1、题目描述
描述
给定一个初始为空的整数集合,每次执行以下两种操作之一:
- 插入操作
1 x
:若 xx 不在集合中,则插入 x;否则输出Already Exist
并忽略操作。 - 提取操作
2 x
:若集合不为空,删除并输出集合中与 xx 绝对差最小的元素;若存在多个候选,则删除并输出其中较小者;若集合为空,则输出Empty
。
输入描述:
第一行包含整数 Q (1 ≦ Q ≦ 105),表示操作次数。
接下来 Q 行,每行包含操作类型 op 和参数 x,格式为 op x,其中 op ∈ {1, 2},0 ≦ x ≦ 109。
输出描述:
对于每次输出类操作(插入重复元素或提取操作),在单独一行输出结果。如插入重复元素则输出 Already Exist
;提取操作输出删除的元素值或 Empty
。
示例1
输入:
5 1 10 1 20 1 15 2 17 2 17
输出:
15 20
说明:
第一次提取删除集合中与 17 距离最小的 15;第二次提取删除剩余元素中与 17 距离最小的 20。
2、解题思路
-
数据结构选择:
- 插入和查找是否存在可以高效使用
std::set
或std::unordered_set
,但需要有序操作来支持提取。 - 提取操作需要找到与
x
最接近的元素,std::set
提供的有序性和lower_bound
操作非常适合。
- 插入和查找是否存在可以高效使用
-
提取操作实现:
- 使用
lower_bound(x)
找到第一个不小于x
的元素it
。 - 可能的候选元素是
it
和prev(it)
(如果存在)。 - 计算这两个候选与
x
的绝对差,选择较小的(若相等,选择较小的元素)。 - 删除并输出选中的元素。
- 使用
-
边界情况:
- 如果集合为空,直接输出
Empty
。 - 如果
lower_bound(x)
返回begin()
或end()
,需要特殊处理以避免越界。
- 如果集合为空,直接输出
3、代码实现
C++
#include <iostream>
#include <set>
using namespace std;
set<int> s;
void insert(int x) {
if (s.count(x)) {
cout << "Already Exist\n";
} else {
s.insert(x);
}
}
void extract(int x) {
if (s.empty()) {
cout << "Empty\n";
return ;
}
auto it = s.lower_bound(x);
int candidate1 = -1, candidate2 = -1;
if (it != s.end()) {
candidate1 = *it;
}
if (it != s.begin()) {
candidate2 = *prev(it);
}
int selected;
if (candidate1 == -1) {
selected = candidate2;
} else if (candidate2 == -1) {
selected = candidate1;
} else {
int diff1 = abs(candidate1 - x);
int diff2 = abs(candidate2 - x);
if (diff1 < diff2) {
selected = candidate1;
} else if (diff1 > diff2) {
selected = candidate2;
} else {
selected = min(candidate1, candidate2);
}
}
s.erase(selected);
cout << selected << '\n';
}
int main() {
int Q, op, x;
cin >> Q;
while (Q--) {
cin >> op >> x;
if (op == 1) {
insert(x);
} else {
extract(x);
}
}
return 0;
}
4、复杂度分析
- 插入操作:
O(log N)
,set
基于红黑树实现。 - 提取操作:
O(log N)
,lower_bound
和erase
均为对数时间。 - 空间复杂度:
O(N)
,存储所有元素。
Q95、数对计数
1、题目描述
描述
给定整数序列 a1, a2, …, aN 和常数 C,统计序列中有多少有序数对 (i, j) 满足 1 ≦ i,j ≦ N 且 ai − aj = C。
输入描述:
第一行输入两个整数 N, C (1 ≦ N ≦ 2×105, 0 ≦ C < 230);
第二行输入 N 个整数 a1, a2, …, aN (0 ≦ ai < 230)。
输出描述:
输出一个整数,表示满足条件的有序数对数量。
示例1
输入:
5 2 1 3 3 4 5
输出:
4
说明:
在此样例中,满足条件的有序数对有 (3,1),(4,1),(4,2),(5,3),共 4 对。
2、解题思路
-
问题转化:
- 我们需要找到满足
a_i = a_j + C
的有序数对(i, j)
。 - 这相当于统计每个元素
a_j
,有多少元素a_i
等于a_j + C
。
- 我们需要找到满足
-
高效统计:
- 可以使用哈希表(如
unordered_map
)来记录每个元素出现的次数。 - 对于每个元素
a_j
,查询哈希表中a_j + C
出现的次数,并累加到总对数中。
- 可以使用哈希表(如
-
特殊情况处理:
- 当
C = 0
时,需要统计每个元素的出现次数,然后对于每个元素a_j
,对数为其出现次数的平方(因为i
和j
可以相同)。 - 当
C ≠ 0
时,直接统计a_j + C
的出现次数即可。
- 当
3、代码实现
C++
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
int n, c;
cin >> n >> c;
vector<int> v(n);
unordered_map<int, int> count;
for (int i = 0; i < n; ++i) {
cin >> v[i];
count[v[i]]++;
}
long long result = 0;
if (c == 0) {
for (auto& [num, cnt] : count) {
result += (long long)cnt * cnt;
}
} else {
for (int num : v) {
result += count[num + c];
}
}
cout << result << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:
O(N)
,遍历序列和哈希表操作均摊为线性时间。 - 空间复杂度:
O(N)
,存储哈希表。
Q96、快乐数
1、题目描述
描述
给定一个正整数,请你判断这个数是不是快乐数。
快乐数:对于一个正整数,每次把他替换为他每个位置上的数字的平方和,如果这个数能变为 1 则是快乐数,如果不可能变成 1 则不是快乐数。
例如:正整数 19
转换过程为 1* 1 + 9 * 9 = 82 , 8 * 8 + 2 * 2 = 68,6 * 6 + 8 * 8 = 100,1 * 1 + 0 * 0 + 0 * 0 = 1 ,所以他是快乐数。
数据范围:输入的正整数满足 1 ≤ n ≤ 109
示例1
输入:
19
返回值:
true
示例2
输入:
111
返回值:
false
2、解题思路
-
快乐数的定义:
- 每次将数字替换为其各位数字的平方和。
- 如果能变为1,则是快乐数;如果进入无限循环(检测到重复出现的数字),则不是快乐数。
-
关键观察:
- 如果一个数字不是快乐数,它会进入一个循环,例如4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4。
- 因此,可以通过检测是否进入这个循环来判断是否不是快乐数。
-
实现方法:
- 使用一个哈希集合(如
unordered_set
)来记录已经出现过的数字,以检测循环。 - 反复计算数字的平方和,直到结果为1或检测到重复。
- 使用一个哈希集合(如
3、代码实现
C++
#include <unordered_set>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @return bool布尔型
*/
bool happynum(int n) {
unordered_set<int> seen;
while (n != 1 && seen.find(n) == seen.end()) {
seen.insert(n);
n = getNext(n);
}
return n == 1;
}
private:
int getNext(int n) {
int sum = 0;
while (n > 0) {
int digit = n % 10;
sum += digit * digit;
n /= 10;
}
return sum;
}
};
4、复杂度分析
- 时间复杂度:
O(log n)
,因为每次计算平方和都会减少数字的位数。 - 空间复杂度:
O(log n)
,哈希集合存储的数字数量与数字位数相关。
Q97、宝石计数
1、题目描述
描述
给定字符串 J 和 S,计算字符串 S 中同时存在于字符串 J 的字符数量。
需要注意的是,字符区分大小写,a 与 A 视为不同的字符。
函数参数:
- 字符串 J,满足 (1 ≦ ∣J∣ ≦ 50),表示不重复的宝石类型;
- 字符串 S,满足 (1 ≦ ∣S∣ ≦ 50),表示手中石头序列。
返回值为一个整数,表示字符串 S 中同时存在于字符串 J 的字符数量。
示例1
输入:
"wangzai","awsl"
返回值:
2
2、解题思路
-
问题分析:
- 字符串
J
中的字符都是不重复的宝石类型。 - 字符串
S
中的字符是手中石头序列,我们需要统计其中有多少个字符存在于J
中。
- 字符串
-
实现方法:
- 将
J
中的字符存储在一个集合中,以方便快速查找。 - 遍历
S
中的每个字符,检查其是否存在于J
的集合中。 - 统计满足条件的字符数量。
- 将
-
优化:
- 使用哈希集合(如
unordered_set
)来存储J
中的字符,这样查找操作的时间复杂度为O(1)
。 - 遍历
S
并统计匹配的字符数量。
- 使用哈希集合(如
3、代码实现
C++
#include <unordered_set>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param jewels string字符串
* @param stones string字符串
* @return int整型
*/
int numJewelsInStones(string jewels, string stones) {
int ret = 0;
unordered_set<int> hash;
for (const auto ch : jewels) {
hash.insert(ch);
}
for (const auto ch : stones) {
if (hash.find(ch) != hash.end()) {
ret++;
}
}
return ret;
}
};
4、复杂度分析
- 时间复杂度:
O(m + n)
,其中m
是字符串J
的长度,n
是字符串S
的长度。遍历J
和S
各一次。 - 空间复杂度:
O(m)
,存储J
中字符的哈希集合。
Q98、不重复数字
1、题目描述
描述
给定长度为 n 的整数序列 a1, a2, …, an。
定义序列 b 初始为空。
- 对每个 i 从 1 到 n:
- 若 ai 在 a1, …, ai−1 中未出现,则将 ai 追加到 b 中;
- 否则忽略。
求最终序列 b。
输入描述:
第一行包含整数 T (1 ≤ T ≤ 50),表示测试数据组数;
接下来 T 组数据,每组数据格式如下:
-
第一行包含整数 n (1 ≤ n ≤ 5 × 104);
-
第二行包含 n 个整数 ai,满足 −231≤ ai < 231。
输出描述:
对每组数据,输出一行,依次输出序列 bb 中的所有元素,元素间以一个空格分隔。
示例1
输入:
3 6 1 2 1 3 2 4 5 5 5 5 5 5 4 10 20 10 20
输出:
1 2 3 4 5 10 20
2、解题思路
-
问题分析:
- 遍历序列
a
中的每个元素,检查其是否在之前的元素中出现过。 - 如果未出现过,则将其加入序列
b
;否则跳过。
- 遍历序列
-
实现方法:
- 使用一个哈希集合(如
unordered_set
)来记录已经出现过的元素。 - 遍历序列
a
,对于每个元素,检查是否在集合中。 - 如果不在集合中,将其加入集合和序列
b
;否则跳过。
- 使用一个哈希集合(如
-
优化:
- 使用
unordered_set
来保证查找操作的平均时间复杂度为O(1)
。 - 遍历序列
a
一次,时间复杂度为O(n)
。
- 使用
3、代码实现
C++
#include <iostream>
#include <unordered_set>
#include <vector>
using namespace std;
int main() {
int t, n, num;
cin >> t;
// cout << t << endl;
while (t--) {
cin >> n;
// cout << n << endl;
unordered_set<int> exist;
vector<int> b;
for (int i = 0; i < n; ++i) {
cin >> num;
if (!exist.count(num)) {
exist.insert(num);
b.push_back(num);
}
}
for (const auto& num : b) {
cout << num << " ";
}
cout << endl;
}
return 0;
}
4、复杂度分析
- 时间复杂度:
O(n)
,其中n
是序列a
的长度。每个元素的查找和插入操作平均为O(1)
。 - 空间复杂度:
O(n)
,存储seen
集合和b
序列。
15、类与结构体
Q99、最厉害的学生
1、题目描述
描述
给定 N 名学生及其信息,包含:
- 姓名 s,仅含小写字母,长度不超过 8;
- 语文、数学、英语成绩 c1, c2, c3,整数且 0 ≦ ci ≦ 150。
定义总分 T = c1 + c2 + c3,求总分最大的学生信息;若存在多名,取输入顺序最小者。
输入描述:
第一行包含整数 N,(1 ≦ N ≦ 1000);
接下来 N 行,每行包含字符串 s 和三个整数 c1,c2,c3,依次表示学生姓名及三门成绩。
输出描述:
输出姓名 s 及对应成绩 c1,c2,c3,以空格分隔。
示例1
输入:
4 alice 100 90 80 david 90 100 80 mary 80 80 100 bob 100 90 80
输出:
alice 100 90 80
说明:
样例中,alice 与 bob 总分均为 270,顺序提前者 alice 为答案。
2、解题思路
-
输入处理:
- 读取学生人数N。
- 读取N行数据,每行包含学生姓名和三门成绩。
-
计算总分:
- 对于每个学生,计算其总分T = c1 + c2 + c3。
-
比较总分:
- 遍历所有学生,记录当前最高总分和对应学生的信息。
- 如果遇到总分更高的学生,更新最高总分和学生信息。
- 如果总分相同,保留输入顺序靠前的学生。
-
输出结果:
- 输出总分最高的学生的姓名和三门成绩。
3、代码实现
C++
#include <iostream>
#include <vector>
using namespace std;
struct student {
string name;
int c1, c2, c3;
int total;
};
int main() {
int n;
cin >> n;
vector<student> s(n);
for (int i = 0; i < n; ++i) {
cin >> s[i].name >> s[i].c1 >> s[i].c2 >> s[i].c3;
s[i].total = s[i].c1 + s[i].c2 + s[i].c3;
}
int max_total = -1;
int index = 0;
for (int i = 0; i < n; ++i) {
if (s[i].total > max_total) {
max_total = s[i].total;
index = i;
}
}
cout << s[index].name << " " << s[index].c1 << " " << s[index].c2 << " " <<
s[index].c3;
return 0;
}
4、复杂度分析
- 时间复杂度:O(N),其中N为学生人数。遍历学生列表一次。
- 空间复杂度:O(N),存储所有学生的信息。
Q100、两点间距离
1、题目描述
描述
给定两个二维平面下的点,求这两个点之间的距离。
示例1
输入:
(1,1),(1,8)
返回值:
7.000
2、解题思路
-
欧几里得距离公式:
-
两点
(x1, y1)
和(x2, y2)
之间的距离d
计算公式为:d = sqrt((x2 − x1)2 + (y2 − y1)2)
-
-
实现步骤:
-
解析输入的两个点的坐标。
-
应用欧几里得距离公式计算距离。
-
输出结果,保留三位小数。
-
3、代码实现
C++
/**
* struct Point {
* int x;
* int y;
* Point(int xx, int yy) : x(xx), y(yy) {}
* };
*/
#include <cmath>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算A点与B点之间的距离
* @param point_A Point类 A点
* @param point_B Point类 B点
* @return double浮点型
*/
double calculateDistance(Point point_A, Point point_B) {
double x = point_A.x - point_B.x;
double y = point_A.y - point_B.y;
return sqrt(x * x + y * y);
}
};
4、复杂度分析
- 时间复杂度:O(1),仅涉及基本的算术运算。
- 空间复杂度:O(1),仅使用少量变量存储坐标和结果。
Q101、学生综合评估系统
1、题目描述
描述
牛客大学正在开发一套新的学生综合评估系统,旨在更全面地反映学生的学业水平和实践能力。
系统对每位学生记录唯一学号 id、学业成绩 A(满分 100 分)和社会实践得分 B(满分 100 分)。
综合分数按以下权重计算:
- 学业成绩占 70%;
- 社会实践得分占 30%;
定义
S = A × 70% + B × 30%
若学生同时满足:
- 学业成绩与社会实践得分之和 A+B>140;
- 综合分数 S≧80;
则评定为 Excellent,否则为 Not excellent。
你需要实现一个函数,接受的参数为学生类型的类,返回值为这个学生是否会被评定为 Excellent 的布尔值。
输入描述:
输入数据仅用于主函数获取数据后调用你实现的函数,你其实可以不用管。
第一行输入整数 N,表示学生人数 (1 ≦ N ≦ 103)。
接下来 N 行,每行输入三个整数 id, A, B,分别表示学号、学业成绩和社会实践得分,满足:
1 ≦ id ≦ 104;
0 ≦ A, B ≦ 100。
输出描述:
输出数据仅用于主函数获取返回值后于评测机交互,你其实可以不用管。
输出 N 行,第 i 行对应第 i 位学生:若评定为优秀,输出 Excellent
;否则输出 Not excellent
。
示例1
输入:
4 1223 95 59 1224 50 7 1473 32 45 1556 86 99
输出:
Excellent Not excellent Not excellent Excellent
2、代码实现
C++
#include<bits/stdc++.h>
using namespace std;
// 定义学生结构体
struct Student {
int id;
int academic_score;
int activity_score;
};
// 评估函数:判断学生是否优秀
bool isExcellent(Student student) {
// TODO: 实现优秀标准的判断逻辑
if (student.academic_score + student.activity_score <= 140) {
return false;
}
if (student.academic_score * 7 + student.activity_score * 3 < 800) {
return false;
}
return true; //true 代表学生优秀
}
//主函数用于读入数据调用函数,请勿修改
int main() {
int n;
cin >> n;
Student student;
for (int i = 1; i <= n; i++) {
cin >> student.id >> student.academic_score >> student.activity_score;
if (isExcellent(student)) cout << "Excellent\n";
else cout << "Not excellent\n";
}
return 0;
}
Q102、点到直线距离
1、题目描述
描述
你需要实现一个函数,给定平面上的点 (a,b),以及一条由另外两个点 (x1,y1) 和 (x2,y2) 确定的直线 L,求点 (a,b) 到直线 L 的距离。
输入描述:
共 2 行。
第一行是两个空格隔开的整数 a,b。
第二行是四个空格隔开的整数 x1,y1,x2,y2。
输出描述:
共一行,一个实数,表示点 (a,b) 到直线 L 的距离,保留两位小数。
示例1
输入:
0 0 -1 1 1 1
输出:
1.00
2、解题思路
-
直线方程:
-
直线可以由两点
(x1, y1)
和(x2, y2)
确定,其一般方程为:(y2 − y1) ⋅ x − (x2 − x1) ⋅ y + (x2 ⋅ y1 − x1 ⋅ y2)=0
可以表示为 A ⋅ x + B ⋅ y + C = 0 A \cdot x + B \cdot y + C = 0 A⋅x+B⋅y+C=0,其中:
A = y2 − y1, B = x1 − x2, C = x2 ⋅ y1 − x1 ⋅ y2
-
-
点到直线的距离公式:
-
点
(a, b)
到直线 A ⋅ x + B ⋅ y + C = 0 A \cdot x + B \cdot y + C = 0 A⋅x+B⋅y+C=0 的距离d
为:d = ∣A⋅a+B⋅b+C∣ / sqrt(A2 + B2)
-
-
实现步骤:
- 解析输入的点
(a, b)
和直线的两点(x1, y1)
和(x2, y2)
。 - 计算直线方程的参数
A
,B
,C
。 - 应用点到直线的距离公式计算距离。
- 输出结果,保留两位小数。
- 解析输入的点
3、代码实现
C++
#include <bits/stdc++.h>
#include <cmath>
using namespace std;
struct point {
double x, y;
point(double A, double B) {
x = A, y = B;
}
point() = default;
};
struct line {
point point_A, point_B;
line(point A, point B) {
point_A = A, point_B = B;
}
line() = default;
};
double getDistance(point P, line L) {
// TODO: 计算点P到直线L的距离
int a, b, c;
a = L.point_B.y - L.point_A.y;
b = L.point_A.x - L.point_B.x;
c = L.point_B.x * L.point_A.y - L.point_A.x * L.point_B.y;
double d2 = (a * P.x + b * P.y + c) * (a * P.x + b * P.y + c) / (a * a + b * b);
return sqrt(d2);
}
int main() {
int a, b, sx, sy, tx, ty;
cin >> a >> b >> sx >> sy >> tx >> ty;
point A(sx, sy), B(tx, ty), C(a, b);
line L(A, B);
printf("%.2lf", getDistance(C, L));
return 0;
}
4、复杂度分析
- 时间复杂度:O(1),仅涉及基本的算术运算。
- 空间复杂度:O(1),仅使用少量变量存储坐标和计算结果。
Q103、三角形面积
1、题目描述
描述
给定平面上不共线的三个整数点,求这三个点构成的三角形的面积。
输入描述:
共 3 行。每行两个整数 xi, yi,表示一个点的坐标。
保证输入的三个点不共线。
输出描述:
共一行,一个实数,表示构成的三角形的面积,保留两位小数。
示例1
输入:
0 0 0 2 1 1
输出:
1.00
2、解题思路
-
海伦公式:
- 计算三角形三边的长度
a
,b
,c
。 - 计算半周长 s = ( a + b + c ) / 2 s = (a + b + c) / 2 s=(a+b+c)/2。
- 面积 A = s q r t ( s ⋅ ( s − a ) ⋅ ( s − b ) ⋅ ( s − c ) ) A = sqrt(s \cdot (s - a) \cdot (s - b) \cdot (s - c)) A=sqrt(s⋅(s−a)⋅(s−b)⋅(s−c))。
- 计算三角形三边的长度
-
向量叉乘法:
- 向量 A B = ( x 2 − x 1 , y 2 − y 1 ) AB = (x2 - x1, y2 - y1) AB=(x2−x1,y2−y1), A C = ( x 3 − x 1 , y 3 − y 1 ) AC = (x3 - x1, y3 - y1) AC=(x3−x1,y3−y1)。
- 叉积 A B × A C = ( x 2 − x 1 ) ⋅ ( y 3 − y 1 ) − ( y 2 − y 1 ) ⋅ ( x 3 − x 1 ) AB × AC = (x2 - x1) \cdot (y3 - y1) - (y2 - y1) \cdot (x3 - x1) AB×AC=(x2−x1)⋅(y3−y1)−(y2−y1)⋅(x3−x1)。
- 面积 A = ∣ A B × A C ∣ / 2 A = |AB × AC| / 2 A=∣AB×AC∣/2。
-
实现步骤:
- 读取三个点的坐标。
- 使用向量叉乘法计算面积。
- 输出结果,保留两位小数。
3、代码实现
C++
#include <bits/stdc++.h>
using namespace std;
struct point {
double x, y;
point(double A, double B) {
x = A, y = B;
}
point() = default;
};
struct triangle {
point a, b, c;
triangle(point A, point B, point C) {
a = A, b = B, c = C;
}
triangle() = default;
};
double getArea(triangle T) {
// TODO: 计算三角形T的面积
return abs((T.b.x - T.a.x) * (T.c.y - T.a.y) - (T.b.y - T.a.y) * (T.c.x - T.a.x)) / 2.0;
}
int main() {
int x, y;
cin >> x >> y;
point a(x, y);
cin >> x >> y;
point b(x, y);
cin >> x >> y;
point c(x, y);
triangle T(a, b, c);
cout << fixed << setprecision(2) << getArea(T) << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:O(1),仅涉及基本的算术运算。
- 空间复杂度:O(1),仅使用少量变量存储坐标和计算结果。
Q104、直线与圆交点间距
1、题目描述
2、解题思路
-
-
首先计算圆心
O
到直线AB
的距离d
。-
使用直线的一般方程
Ax + By + C = 0
,其中:A = y2 − y1, B = x1 − x2, C = x2 ⋅ y1 − x1 ⋅ y2
-
圆心
(ox, oy)
到直线的距离公式为:d = ∣A⋅a+B⋅b+C∣ / sqrt(A2 + B2)
-
-
-
两点间距离:
- 如果
d < r
,则直线与圆相交于两点,两点间的距离为:∣AB∣ = 2 ⋅ (r2 − d2) - 如果
d == r
,则直线与圆相切,两点重合,距离为0
。
- 如果
-
实现步骤:
- 计算直线方程的参数
A
,B
,C
。 - 计算圆心到直线的距离
d
。 - 根据
d
和r
的关系计算两点间的距离。
- 计算直线方程的参数
3、代码实现
C++
#include <bits/stdc++.h>
using namespace std;
struct point {
double x, y;
point(double A, double B) {
x = A, y = B;
}
point() = default;
};
struct line {
point point_A, point_B;
line(point A, point B) {
point_A = A, point_B = B;
}
line() = default;
};
struct Circle {
point O;
int r;
Circle(point A, int B) {
O = A, r = B;
}
Circle() = default;
};
double getDistance(const Circle& circle, const line& l) {
// 请在这里实现你的代码
double ox = circle.O.x, oy = circle.O.y;
double r = circle.r;
double x1 = l.point_A.x, y1 = l.point_A.y;
double x2 = l.point_B.x, y2 = l.point_B.y;
// 计算直线方程的参数 A, B, C
double A = y2 - y1;
double B = x1 - x2;
double C = x2 * y1 - x1 * y2;
// 计算圆心到直线的距离 d
double d = abs(A * ox + B * oy + C) / sqrt(A * A + B * B);
// 计算两点间的距离
if (d < r) {
return 2 * sqrt(r * r - d * d);
} else if (d == r) {
return 0.0;
}
// 根据题目备注,直线与圆不相离,所以不需要处理 d > r 的情况
return 0.0;
}
int main() {
double ox, oy, r;
double x1, y1, x2, y2;
cin >> ox >> oy >> r;
cin >> x1 >> y1 >> x2 >> y2;
point center(ox, oy);
Circle circle(center, (int)r);
point p1(x1, y1);
point p2(x2, y2);
line l(p1, p2);
double result = getDistance(circle, l);
cout << fixed << setprecision(6) << result << endl;
return 0;
}
4、复杂度分析
- 时间复杂度:O(1),仅涉及基本的算术运算。
- 空间复杂度:O(1),仅使用少量变量存储坐标和计算结果。
Q105、两直线交点
1、题目描述
描述
给定两个直线,求两条直线的交点。
你需要实现一个函数,接受的参数为两个直线,返回值为点类型的两个直线的交点。
输入描述:
输入包含两行:
第一行包含四个正整数,分别为 A 点的横纵坐标的 B 点的横纵坐标。
第二行包含四个正整数,分别为 C 点的横纵坐标的 D 点的横纵坐标。
数据保证 A、B 不重合,C、D 不重合。
输出描述:
输出直线 AB 和直线 CD 的交点,如果两直线无交点,则输出 -1 -1 。
数据保证交点的横纵坐标的绝对值均在 104 以内。
注意,只要您的答案与标准答案之差在 10−6 以内,就会被认为是正确的。
示例1
输入:
0 0 1 1 0 2 2 0
输出:
1.000000 1.000000
2、解题思路
-
直线方程:
-
直线可以由两点
(x1, y1)
和(x2, y2)
确定,其一般方程为:(y2 − y1) ⋅ x − (x2 − x1) ⋅ y + (x2 ⋅ y1 − x1 ⋅ y2) = 0
可以表示为 A ⋅ x + B ⋅ y + C = 0 A \cdot x + B \cdot y + C = 0 A⋅x+B⋅y+C=0,其中:
A = y2 − y1, B = x1 − x2, C = x2 ⋅ y1 − x1 ⋅ y2
-
-
交点计算:
-
两条直线
A1x + B1y + C1 = 0
和A2x + B2y + C2 = 0
的交点(x, y)
可以通过解线性方程组得到: -
如果分母 A 1 ⋅ B 2 − A 2 ⋅ B 1 A1 \cdot B2 - A2 \cdot B1 A1⋅B2−A2⋅B1 为 0,则两条直线平行或重合,无交点。
-
-
实现步骤:
- 计算两条直线的方程参数
A1, B1, C1
和A2, B2, C2
。 - 计算分母 D = A 1 ⋅ B 2 − A 2 ⋅ B 1 D = A1 \cdot B2 - A2 \cdot B1 D=A1⋅B2−A2⋅B1。
- 如果
D == 0
,则返回-1 -1
,否则计算交点坐标(x, y)
。
- 计算两条直线的方程参数
3、代码实现
C++
#include<bits/stdc++.h>
using namespace std;
struct point {
double x, y;
point(double A, double B) {
x = A, y = B;
}
point() = default;
};
struct line {
point point_A, point_B;
line(point A, point B) {
point_A = A, point_B = B;
}
line() = default;
};
point findMeetingPoint(line line_A, line line_B) {
// TODO:求直线 line_A 与 line_B 的交点
double x1 = line_A.point_A.x, y1 = line_A.point_A.y;
double x2 = line_A.point_B.x, y2 = line_A.point_B.y;
double x3 = line_B.point_A.x, y3 = line_B.point_A.y;
double x4 = line_B.point_B.x, y4 = line_B.point_B.y;
// 计算直线方程的参数
double A1 = y2 - y1;
double B1 = x1 - x2;
double C1 = x2 * y1 - x1 * y2;
double A2 = y4 - y3;
double B2 = x3 - x4;
double C2 = x4 * y3 - x3 * y4;
// 计算分母
double D = A1 * B2 - A2 * B1;
// 判断是否平行或重合
if (fabs(D) < 1e-8) {
return point(-1, -1);
}
// 计算交点
double x = (B1 * C2 - B2 * C1) / D;
double y = (A2 * C1 - A1 * C2) / D;
std::cout << std::fixed << std::setprecision(6);
return point(x, y);
}
int main() {
point A, B, C, D;
cin >> A.x >> A.y >> B.x >> B.y >> C.x >> C.y >> D.x >> D.y;
line AB = line(A, B);
line CD = line(C, D);
point ans = findMeetingPoint(AB, CD);
cout << ans.x << " " << ans.y;
return 0;
}
4、复杂度分析
- 时间复杂度:O(1),仅涉及基本的算术运算。
- 空间复杂度:O(1),仅使用少量变量存储坐标和计算结果。
16、优先队列
Q106、【模板】整数优先队列
1、题目描述
描述
给定一个初始为空的多重集合 S,支持以下操作:
-
插入操作:给定整数 x,将 x 加入集合 S;
-
查询操作:输出 S 中的最小元素;
-
删除操作:删除 S 中的一个最小元素。
输入描述:
第一行包含整数 n (1 ≤ n ≤ 106),表示操作总数。
接下来 n 行,每行包含整数 op 和(可选的)x,其含义如下:
- 若 op=1,则后接整数 x (1 ≤ x < 231);
- 若 op=2 或 op=3,则仅包含 op。
输出描述:
对于每个查询操作(op=2),输出一行,包含当前多重集合中最小元素。
示例1
输入:
7 1 5 1 3 2 1 10 2 3 2
输出:
3 3
2、解题思路
-
数据结构选择:
- 需要高效地插入、查询和删除最小元素,可以使用
std::multiset
,因为它自动维护元素的排序并且允许重复元素。 - 另一种方法是使用
std::priority_queue
,但它不支持直接删除最小元素(除非使用特定的技巧)。
- 需要高效地插入、查询和删除最小元素,可以使用
-
操作实现:
- 插入操作:直接将
x
插入multiset
。 - 查询操作:输出
multiset
的第一个元素(即最小元素)。 - 删除操作:删除
multiset
的第一个元素。
- 插入操作:直接将
-
输入输出处理:
- 根据
op
的值执行相应的操作。 - 对于查询操作,输出最小元素;如果集合为空,应特殊处理(但题目没有说明,假设集合非空)。
- 根据
3、代码实现
C++
#include <functional>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
int n, op, num;
priority_queue<int, vector<int>, greater<int>> q;
cin >> n;
while (n--) {
cin >> op;
if (op == 1) {
cin >> num;
q.push(num);
} else if (op == 2) {
cout << q.top() << endl;
} else if (op == 3) {
q.pop();
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:
- 插入操作:
O(log n)
。 - 查询和删除最小元素:
O(1)
。 - 总体复杂度:
O(n log n)
。
- 插入操作:
- 空间复杂度:
O(n)
,存储所有元素。
Q107、结构体优先队列
1、题目描述
描述
期末考试结束了,老师开始忙着给每个同学登记成绩。这次考试共有语文、数学和外语三门科目。登记过程中有如下三种操作:
- 操作
1 x y z
:登记一位同学的成绩,语文 x 分、数学 y 分、外语 z 分; - 操作
2
:输出当前成绩最好的同学的三门成绩; - 操作
3
:删除成绩最好的同学的记录(若有并列,则只删除一人)。
“成绩最好” 的判定规则为:首先比较总成绩 x+y+z,总成绩高者更好;若相同,则比较语文成绩 x;若仍相同,则比较数学成绩 y;再相同时比较外语成绩 z。
输入描述:
第一行输入一个整数 n(1 ≦ n ≦ 106),表示操作次数。
接下来 n 行,每行是一次操作:
- 若操作类型为
1
,则形式为1 x y z
,其中 1 ≦ x, y, z <100; - 若操作类型为
2
或3
,则形式为单个整数2
或3
。
输出描述:
对于每次操作 2
,在一行中输出 x y z
,表示该次查询时成绩最好的同学的语文、数学和外语成绩。
示例1
输入:
10 1 93 27 6 2 3 1 31 46 2 1 100 85 84 2 2 1 2 40 3 2 2
输出:
93 27 6 100 85 84 100 85 84 100 85 84 100 85 84
说明:
∙ 操作 1 登记 (93,27,6),操作 2 输出当前最优 (93,27,6); ∙ 操作 3 删除该记录; ∙ 接着操作 1 登记 (31,46,2),操作 1 登记 (100,85,84); ∙ 连续两次操作 2 均输出 (100,85,84); ∙ 操作 1 登记 (2,40,3) 后的两次操作 2 仍输出 (100,85,84),因为它仍是最优。
2、代码实现
C++
#include<bits/stdc++.h>
using namespace std;
struct node {
int chinese, math, english, sum;
node(int c, int m, int e) : chinese(c), math(m), english(e) {
sum = c + m + e;
}
};
bool operator<(node a, node b) {
// TODO: 实现比较逻辑,按照总分、语文、数学、英语的优先级排序
if (a.sum != b.sum) {
return a.sum < b.sum;
} else if (a.chinese != b.chinese) {
return a.chinese < b.chinese;
} else if (a.math != b.math) {
return a.math < b.math;
} else {
return a.english < b.english;
}
}
priority_queue<node> s;
void insertValue(int chinese, int math, int english) {
// TODO: 实现插入操作
s.push(node(chinese, math, english));
}
void deleteValue() {
// TODO: 实现删除操作
if (!s.empty()) {
s.pop();
}
}
node getTop() {
// TODO: 返回成绩最好的学生
return s.top();
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int q, op;
int x, y, z;
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x >> y >> z;
insertValue(x, y, z);
}
if (op == 2) {
node tmp = getTop();
cout << tmp.chinese << " " << tmp.math << " " << tmp.english << endl;
}
if (op == 3) {
deleteValue();
}
}
return 0;
}
Q108、字符串优先队列
1、题目描述
描述
给定一个字符串序列,初始为空,请支持下面三种操作:
-
操作 1:给定一个字符串 s,将 s 加入到序列中;
-
操作 2:输出序列中字典序最小的字符串;
-
操作 3:删除序列中字典序最小的字符串(若有多个字典序最小的,只删除 1 个)。
输入描述:
第一行输入一个整数 n(1 ≦ n ≦ 106),表示操作次数。
接下来 n 行,每行表示一次操作。每行首先有一个整数 op 表示操作类型:
-
若 op=1,则后接一个字符串 s,表示将 s 加入序列;
-
若 op=2,则表示输出序列中字典序最小的字符串;
-
若 op=3,则表示删除序列中字典序最小的字符串。
保证 op ∈ {1,2,3},且所有加入字符串的长度之和不超过 106。
输出描述:
对于每个操作 2,在一行中输出一个字符串,表示操作时序列中字典序最小的字符串。
示例1
输入:
5 1 2 1 5 2 3 2
输出:
2 5
说明:
操作顺序: 1. 加入 "abc",序列 = ["abc"]; 2. 加入 "cda",序列 = ["abc","cda"]; 3. 输出最小 = "abc"; 4. 删除最小("abc"),序列 = ["cda"]; 5. 输出最小 = "cda"。
2、解题思路
-
数据结构选择:
- 需要高效地插入、查询和删除字典序最小的字符串,可以使用
std::multiset
或std::priority_queue
。 - 本题选用
std::priority_queue
并自定义比较函数,使其维护字典序最小的字符串在堆顶。
- 需要高效地插入、查询和删除字典序最小的字符串,可以使用
-
比较逻辑:
- 使用
std::greater<string>
作为比较函数,确保堆顶是字典序最小的字符串。
- 使用
-
操作实现:
- 插入操作:直接将字符串插入优先队列。
- 查询操作:返回堆顶字符串。
- 删除操作:删除堆顶字符串。
3、代码实现
C++
#include<bits/stdc++.h>
using namespace std;
priority_queue<string, vector<string>, greater<string>> s;
void insertValue(string x) {
// TODO: 实现插入操作
s.push(x);
}
void deleteValue() {
// TODO: 实现删除操作
s.pop();
}
string getTop() {
// TODO: 返回字典序最小的字符串
return s.top();
}
int main() {
int q, op;
string x;
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x;
insertValue(x);
}
if (op == 2) {
cout << getTop() << endl;
}
if (op == 3) {
deleteValue();
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:
- 插入操作:
O(log n)
。 - 查询和删除最小元素:
O(1)
。 - 总体复杂度:
O(n log n)
。
- 插入操作:
- 空间复杂度:
O(n)
,存储所有字符串。
Q109、两端问优先队列
1、题目描述
描述
给定一个序列,初始为空,请支持以下五种操作:
- 操作 1:将整数 xx 插入序列中;
- 操作 2:输出序列中的最小值;
- 操作 3:输出序列中的最大值;
- 操作 4:删除序列中的最小值(若有多个,只删除一个);
- 操作 5:删除序列中的最大值(若有多个,只删除一个)。
输入描述:
第一行输入一个整数 n(1 ≦ n ≦ 105),表示操作次数。
接下来 n 行,每行表示一次操作,格式如下:
若 op=1,则后接一个整数 x(1 ≦ x < 231),表示插入 x;
若 op=2,表示查询并输出当前序列中的最小值;
若 op=3,表示查询并输出当前序列中的最大值;
若 op=4,表示删除当前序列中的最小值;
若 op=5,表示删除当前序列中的最大值。
输出描述:
对于每次操作 2 和操作 3,输出一行一个整数,表示查询结果。
示例1
输入:
10 1 97 3 5 1 78 3 5 1 68 3 5 1 49
输出:
97 78 68
说明:
操作序列中:初始序列空;插入 97;查询最小值 97;删除最小值;插入 78;查询最小值 78;删除最小值;插入 68;查询最小值 68;删除最小值;插入 49。 共 3 次查询,结果依次为 97,78,68。
2、解题思路
-
数据结构选择:
- 需要高效地插入、查询和删除最小值和最大值,可以使用
std::multiset
,因为它自动维护元素的排序并且允许重复元素。 - 另一种方法是使用两个
std::priority_queue
,一个维护最小值(小顶堆),一个维护最大值(大顶堆),但这样删除操作会比较复杂。
- 需要高效地插入、查询和删除最小值和最大值,可以使用
-
操作实现:
- 插入操作:直接将
x
插入multiset
。 - 查询最小值:输出
multiset
的第一个元素(即最小元素)。 - 查询最大值:输出
multiset
的最后一个元素(即最大元素)。 - 删除最小值:删除
multiset
的第一个元素。 - 删除最大值:删除
multiset
的最后一个元素。
- 插入操作:直接将
-
输入输出处理:
- 根据
op
的值执行相应的操作。 - 对于查询操作,输出最小或最大元素。
- 根据
3、代码实现
C++
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
multiset<int> s;
for (int i = 0; i < n; ++i) {
int op;
cin >> op;
if (op == 1) {
int x;
cin >> x;
s.insert(x);
} else if (op == 2) {
cout << *s.begin() << '\n';
} else if (op == 3) {
cout << *s.rbegin() << '\n';
} else if (op == 4) {
if (!s.empty()) {
s.erase(s.begin());
}
} else if (op == 5) {
if (!s.empty()) {
auto it = s.end();
--it;
s.erase(it);
}
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:
- 插入操作:
O(log n)
。 - 查询最小值和最大值:
O(1)
。 - 删除最小值和最大值:
O(1)
。 - 总体复杂度:
O(n log n)
。
- 插入操作:
- 空间复杂度:
O(n)
,存储所有元素。
Q110、和+和
1、题目描述
描述
对于给定的由 n 个整数构成的数组 {a1, a2, …, an} 和 {b1, b2, …, bn},从小到大选择 2 × m (≦ n) 个下标(从 1 开始编号),满足 i1, i2, …, i2m (1 ≦ i1 < i2 < ⋯ < i2×m ≦ n),使得以下表达式的值最小:
(ai1 + ai2 + ⋯ + aim) + (bim+1 + bim+2 + ⋯ + bi2×m)
直接输出这个最小值。
输入描述:
第一行输入两个整数 n, m (2 ≦ n ≦ 2 × 105; 2 ≦ 2 × m ≦ n) 代表数组中的元素数量、需要选择的下标数量。
第二行输入 n 个整数 a1, a2, …, an (−109 ≦ ai ≦ 109) 代表数组 a 中的元素。
第三行输入 n 个整数 b1, b2, …, bn (−109 ≦ bi ≦ 109 代表数组 b 中的元素。
输出描述:
在一行上输出一个整数,代表所求式子的最小值。
示例1
输入:
3 1 3 1 2 5 6 4
输出:
5
说明:
选择下标 2,3,得到式子的最小值 a2+b3=1+4=5。
示例2
输入:
4 2 -2 2 1 -1 -1 1 -2 2
输出:
0
说明:
在这个样例中,我们有且仅有唯一的选择,即选择下标 1,2,3,4,得到式子的最小值 a1+a2+b3+b4=−2+2−2+2=0。
示例3
输入:
6 2 1 2 1 3 3 9 3 3 9 1 2 1
输出:
4
2、代码实现
C++
#include <cstdio>
#include <iostream>
#include <limits>
#include <queue>
#include <string>
#include <vector>
using namespace std;
int GetNum() {
char ch = getchar();
string temp;
while (ch != ' ' && ch != '\n') {
temp .push_back(ch);
ch = getchar();
}
return stoi(temp);
}
void Read(vector<int>& v, int n) {
for (int i = 0; i < n; ++i) {
v[i] = GetNum();
}
}
int main() {
int n = 0;
int m = 0;
cin >> n >> m;
getchar();
vector<int> va(n);
vector<int> vb(n);
vector<long long> minA(n); //minA[i]表示a[0, i]之间的最小的m个数的和
vector<long long> minB(n); //minB[i]表示b[i,n-1]之间的最小的m个数的和
priority_queue<int> pqA; //存放在a里面取的最小的m个数
priority_queue<int> pqB; //存放在b里面取的最小的m个数
Read(va, n);
Read(vb, n);
long long sum = numeric_limits<long long>::max(); //记录表达式的值
//预处理,分别计算出minA和minB
for (int i = 0; i < n; ++i) {
if (pqA.size() < m) {
pqA.push(va[i]);
minA[i] = (i == 0 ? va[i] : minA[i - 1] + va[i]);
} else {
if (pqA.top() > va[i]) {
int temp = pqA.top();
pqA.pop();
pqA.push(va[i]);
minA[i] = minA[i - 1] - temp + va[i];
} else {
minA[i] = minA[i - 1];
}
}
}
for (int i = n - 1; i >= 0; --i) {
if (pqB.size() < m) {
pqB.push(vb[i]);
minB[i] = (i == n - 1 ? vb[i] : minB[i + 1] + vb[i]);
} else {
if (pqB.top() > vb[i]) {
int temp = pqB.top();
pqB.pop();
pqB.push(vb[i]);
minB[i] = minB[i + 1] - temp + vb[i];
} else {
minB[i] = minB[i + 1];
}
}
}
//在a的 0 ~ k 下标处取最小的m个数,在b的 k+1 ~ n-1 下标处取最小的m个数
for (int k = m - 1; k <= n - m - 1; ++k) {
long long temp = minA[k] + minB[k + 1];
if (temp < sum) {
sum = temp;
}
}
cout << sum << endl;
}
17、链表
Q111、两两交换链表中的结点
1、题目描述
描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
链表中的元素个数不超过 100 个。
示例1
输入:
{7,0,1,2}
返回值:
{0,7,2,1}
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* swapPairs(ListNode* head) {
ListNode hair(0), *cur = &hair;
hair.next = head;
while (cur->next && cur->next->next) {
ListNode* cur1 = cur->next;
ListNode* cur2 = cur1->next;
cur->next = cur2;
cur1->next = cur2->next;
cur2->next = cur1;
cur = cur1;
}
return hair.next;
}
};
Q112、移除链表元素
1、题目描述
描述
给定一个链表,请你删除链表中所有值等于val的元素。
示例1
输入:
{1,1,4,5,1,4},4
返回值:
{1,1,5,1}
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param val int整型
* @return ListNode类
*/
ListNode* removeElements(ListNode* head, int val) {
// write code here
ListNode hair(0), *cur = &hair;
hair.next = head;
while (cur->next) {
ListNode* tmp = cur->next;
if (tmp->val == val) {
cur->next = tmp->next;
delete tmp;
} else {
cur = cur->next;
}
}
return hair.next;
}
};
Q113、反转链表
1、题目描述
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* ReverseList(ListNode* head) {
// write code here
ListNode* prev = nullptr, *cur = head, *tmp = nullptr;
while (cur) {
tmp = cur;
cur = cur->next;
tmp->next = prev;
prev = tmp;
}
return prev;
}
};
Q114、链表序列化
1、题目描述
描述
你需要将一个单链表,按照从表头向表尾的顺序转化为一个序列。
示例1
输入:
{1,1,4,5,1,4}
返回值:
[1,1,4,5,1,4]
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @return int整型vector
*/
vector<int> listnodeToVector(ListNode* head) {
// write code here
vector<int> ret;
ListNode* cur = head;
while (cur) {
ret.push_back(cur->val);
cur = cur->next;
}
return ret;
}
};
Q115、序列链表化
1、题目描述
描述
你需要将一个序列,按照从表头向表尾的顺序转化为一个单链表。
示例1
输入:
[1,3,8]
返回值:
{1,3,8}
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param arr int整型vector
* @return ListNode类
*/
ListNode* vectorToListnode(vector<int>& arr) {
// write code here
ListNode head(0), *cur = &head, *newnode;
for (const auto& num : arr) {
newnode = new ListNode(num);
cur->next = newnode;
cur = cur->next;
}
return head.next;
}
};
Q116、合并两个排序的链表
1、题目描述
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead1 ListNode类
* @param pHead2 ListNode类
* @return ListNode类
*/
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
// write code here
ListNode head(0), *cur = &head, *cur1 = pHead1, *cur2 = pHead2;
while (cur1 && cur2) {
if (cur1->val < cur2->val) {
cur->next = cur1;
cur1 = cur1->next;
} else {
cur->next = cur2;
cur2 = cur2->next;
}
cur = cur->next;
}
if (cur1) {
cur->next = cur1;
} else {
cur->next = cur2;
}
return head.next;
}
};
Q117、链表相交
1、题目描述
2、代码实现
C++
#include <bits/stdc++.h>
#include <unordered_set>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
// 在这里补充代码
unordered_set<ListNode*> hash;
ListNode* cur = headA;
while (cur) {
hash.insert(cur);
cur = cur->next;
}
cur = headB;
while (cur) {
if (hash.count(cur)) {
break;
}
cur = cur->next;
}
return cur;
}
//你不需要修改主函数内的代码!
int main() {
// 读入数据
int lenA, lenB, commonLen;
cin >> lenA >> lenB >> commonLen;
// 构建链表
vector<ListNode*> nodesA(lenA - commonLen);
vector<ListNode*> nodesB(lenB - commonLen);
vector<ListNode*> nodesCommon(commonLen);
// 读入并创建链表A的独立部分
for (int i = 0; i < lenA - commonLen; i++) {
int val;
cin >> val;
nodesA[i] = new ListNode(val);
if (i > 0) nodesA[i - 1]->next = nodesA[i];
}
// 读入并创建链表B的独立部分
for (int i = 0; i < lenB - commonLen; i++) {
int val;
cin >> val;
nodesB[i] = new ListNode(val);
if (i > 0) nodesB[i - 1]->next = nodesB[i];
}
// 读入并创建公共部分
for (int i = 0; i < commonLen; i++) {
int val;
cin >> val;
nodesCommon[i] = new ListNode(val);
if (i > 0) nodesCommon[i - 1]->next = nodesCommon[i];
}
// 连接链表
ListNode* headA = nullptr;
ListNode* headB = nullptr;
if (lenA - commonLen > 0) {
headA = nodesA[0];
if (commonLen > 0) nodesA.back()->next = nodesCommon[0];
} else if (commonLen > 0) {
headA = nodesCommon[0];
}
if (lenB - commonLen > 0) {
headB = nodesB[0];
if (commonLen > 0) nodesB.back()->next = nodesCommon[0];
} else if (commonLen > 0) {
headB = nodesCommon[0];
}
// 调用函数获取结果
ListNode* result = getIntersectionNode(headA, headB);
// 输出结果
if (result == nullptr) {
cout << "null" << endl;
} else {
cout << result->val << endl;
}
// 清理内存
for (auto node : nodesA) delete node;
for (auto node : nodesB) delete node;
for (auto node : nodesCommon) delete node;
return 0;
}
Q118、判断一个链表是否为回文结构
1、题目描述
描述
给定一个链表,请判断该链表是否为回文结构。
回文是指该字符串正序逆序完全一致。
数据范围: 链表节点数 0 ≤ n ≤ 105,链表中每个节点的值满足 ∣val∣ ≤ 107
示例1
输入:
{1}
返回值:
true
示例2
输入:
{2,1}
返回值:
false
说明:
2->1
示例3
输入:
{1,2,2,1}
返回值:
true
说明:
1->2->2->1
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类 the head
* @return bool布尔型
*/
bool isPail(ListNode* head) {
// write code here
vector<int> v;
ListNode* cur = head;
while (cur) {
v.push_back(cur->val);
cur = cur->next;
}
int left = 0, right = v.size() - 1;
while (left < right) {
if (v[left] != v[right]) {
return false;
}
++left;
--right;
}
return true;
}
};
Q119、插队
1、题目描述
描述
在熙熙攘攘的商业街区,一家人气爆棚的网红奶茶店前总是大排长龙。然而,在这有序的队伍中,偶尔也会出现不和谐的插队行为——插队不仅破坏公平排队的秩序,也引发其他顾客的不满与抱怨。
假设店前有 nn 位顾客按从前到后依次排队,初始队列为 s1, s2, …, sn。随后发生 m 次插队事件。在第 i 次事件中,名为 xi 的顾客会移动到名为 yi 的顾客前面。
请你模拟所有插队事件,输出最终队列中顾客的排队顺序。
输入描述:
第一行输入两个整数 n,m,用空格隔开,表示顾客人数和插队事件次数,满足
2 ≦ n ≦ 2 × 105, 1 ≦ m ≦ 2 × 105。
第二行输入 n 个字符串 s1, s2, …, sn,用空格隔开,表示初始排队顺序。每个字符串长度 1 ≦ ∣si∣ ≦ 10,仅由小写字母组成;所有字符串两两互不相同。
接下来 m 行,每行输入两个字符串 xi, yi,用空格隔开,表示第 i 次插队事件中,名为 xi 的顾客插入到名为 yi 的顾客前。保证 xi, yi 均在初始队列中出现,且 xi ≠ yi。
输出描述:
输出一行 n 个字符串,用空格隔开,表示所有插队事件结束后队列中顾客的顺序。
示例1
输入:
4 6 alpha bravo charlie delta bravo alpha charlie alpha delta alpha charlie bravo delta bravo charlie delta
输出:
charlie delta bravo alpha
说明:
初始队列:alpha bravo charlie delta 事件 1:bravo alpha charlie delta 事件 2:bravo charlie alpha delta 事件 3:bravo charlie delta alpha 事件 4:charlie bravo delta alpha 事件 5:charlie delta bravo alpha 事件 6:charlie delta bravo alpha
示例2
输入:
3 2 amy bob cath cath amy bob amy
输出:
cath bob amy
说明:
初始队列:amy bob cath 事件 1:amy cath bob 事件 2:cath bob amy
2、解题思路
-
数据结构选择:
- 频繁的插入和删除操作需要使用高效的链表结构。
std::list
在插入和删除操作上的时间复杂度为O(1)
。 - 需要快速查找特定顾客的位置,可以使用哈希表(
std::unordered_map
)来存储顾客名字到链表迭代器的映射。
- 频繁的插入和删除操作需要使用高效的链表结构。
-
操作实现:
- 初始化时将顾客顺序存入链表,并记录每个顾客的迭代器。
- 对于每次插队事件:
- 使用哈希表找到
x_i
和y_i
的迭代器。 - 从链表中移除
x_i
。 - 在
y_i
之前插入x_i
。 - 更新哈希表中
x_i
的迭代器。
- 使用哈希表找到
-
输出结果:
- 遍历链表,输出最终的顾客顺序。
3、代码实现
C++
#include <iostream>
#include <list>
#include <string>
#include <unordered_map>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
list<string> queue;
unordered_map<string, list<string>::iterator> pos;
string name, x, y;
for (int i = 0; i < n; ++i) {
cin >> name;
queue.push_back(name);
pos[name] = prev(queue.end());
}
for (int i = 0; i < m; ++i) {
cin >> x >> y;
auto it_x = pos[x];
auto it_y = pos[y];
if (it_x != it_y) {
queue.erase(it_x);
pos[x] = queue.insert(it_y, x);
}
}
for (const string& name : queue) {
cout << name << " ";
}
return 0;
}
4、复杂度分析
- 时间复杂度:
- 初始化队列和哈希表:
O(n)
。 - 每次插队操作:
O(1)
(平均情况下)。 - 总共
m
次操作:O(m)
。 - 输出结果:
O(n)
。 - 总的时间复杂度:
O(n + m)
。
- 初始化队列和哈希表:
- 空间复杂度:
- 存储队列和哈希表:
O(n)
。
- 存储队列和哈希表:
18、哈希
Q120、两数之和
1、题目描述
描述
给定整数序列 a = [a1, a2, …, an] 和目标整数 T,寻找一对下标 (i, j) 满足 1 ≦ i < j ≦ n 且 ai + aj = T。
测试数据保证恰有一组解,你需要求出对应的下标对 (i, j) 作为序列并返回。
传入参数:
- 整数序列 a,长度满足 2 ≦ n ≦ 104 且 0 ≦ ai ≦ 109;
- 目标整数 T,满足 0 ≦ T ≦ 109。
返回值:
一对整数下标 (i, j) 构成的序列,满足 ai + aj = T。
示例1
输入:
[0,7,2,1],9
返回值:
[1,2]
2、解题思路
- 暴力法:
- 最简单的方法是使用双重循环遍历所有可能的
(i, j)
组合,检查是否满足a[i] + a[j] == T
。时间复杂度为O(n^2)
,在n
较大时可能不够高效。
- 最简单的方法是使用双重循环遍历所有可能的
- 哈希表法:
- 使用哈希表(或字典)来存储已经遍历过的元素及其下标。
- 对于每个元素
a[j]
,检查T - a[j]
是否存在于哈希表中。如果存在,则返回对应的下标i
和j
。时间复杂度为O(n)
。
- 双指针法:
- 如果序列是有序的,可以使用双指针法,一个指针从头部开始,另一个从尾部开始,向中间移动,寻找满足
a[i] + a[j] == T
的组合。时间复杂度为O(n log n)
(排序时间)或O(n)
(已排序情况下)。
- 如果序列是有序的,可以使用双指针法,一个指针从头部开始,另一个从尾部开始,向中间移动,寻找满足
方法选择
由于题目没有说明序列是否有序,且哈希表法的时间复杂度最优,我们选择哈希表法。
3、代码实现
C++
#include <unordered_map>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @param target int整型
* @return int整型vector
*/
vector<int> twoSum(vector<int>& nums, int target) {
// write code here
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); ++i) {
hash[nums[i]] = i;
}
for (int i = 0; i < nums.size(); ++i) {
if (hash.count(target - nums[i])) {
return {i, hash[target - nums[i]]};
}
}
return {-1, -1};
}
};
4、复杂度分析
- 时间复杂度:
O(n)
,因为只需要遍历一次数组。 - 空间复杂度:
O(n)
,用于存储哈希表。
Q121、字符串哈希
1、题目描述
描述
给定 N 个字符串 s1, s2, …, sN,其中每个字符串仅由数字和大小写字母组成且区分大小写。请计算这 N 个字符串中不同字符串的个数。
输入描述:
第一行输入整数 N (1 ≦ N ≦ 104)。
接下来 N 行,每行输入一个字符串 si,满足 1 ≦ ∣si∣ ≦ 1500。
输出描述:
输出一个整数,表示不同字符串的数量。
示例1
输入:
6 Hello World hello HELLO Code Hello
输出:
5
说明:
在该样例中,输入的字符串集合为 {Hello,World,hello,HELLO,Code,Hello},不同字符串共有 5 个。
2、解题思路
-
数据结构选择:
- 使用哈希集合(
std::unordered_set
)来存储字符串,因为集合会自动去重。 - 遍历所有字符串,将每个字符串插入到集合中,集合会自动处理重复的字符串。
- 使用哈希集合(
-
算法选择:
- 遍历每个字符串,插入到集合中。
- 最终集合的大小即为不同字符串的数量。
-
复杂度分析:
- 插入操作的平均时间复杂度为
O(1)
,N
次插入操作的时间复杂度为O(N)
。 - 空间复杂度为
O(N)
,用于存储集合中的字符串。
- 插入操作的平均时间复杂度为
3、代码实现
C++
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;
int main() {
int n;
cin >> n;
string s;
unordered_set<string> hash;
while (n--) {
cin >> s;
hash.insert(s);
}
cout << hash.size() << endl;
return 0;
}
Q122、大整数哈希
1、题目描述
2、解题思路
-
数据结构选择:
- 由于
x
的范围很大(0 ≤ x < 2^64
),直接使用数组存储不可行。 - 使用哈希表(如
std::unordered_map
)来存储f(x)
的当前值,初始时所有x
对应的值为0
。 - 哈希表的插入和查询操作的平均时间复杂度为
O(1)
,适合处理大规模数据。
- 由于
-
算法流程:
- 初始化一个空的哈希表
f
。 - 遍历每个操作
(x, y)
:- 查询当前
f[x]
的值作为ans_i
。 - 更新
f[x] = y
。 - 累加
i * ans_i
到总和中。
- 查询当前
- 最终总和自动对
2^64
取模(因为使用unsigned long long
类型存储)。
- 初始化一个空的哈希表
-
注意事项:
- 由于
x
和y
的范围是0 ≤ x, y < 2^64
,使用unsigned long long
类型存储。 - 总和的计算也需要使用
unsigned long long
类型,以避免溢出。
- 由于
3、代码实现
C++
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unsigned long long n, x, y, i = 1;
cin >> n;
unordered_map<unsigned long long, unsigned long long> hash;
unsigned long long ret = 0;
while (n--) {
cin >> x >> y;
ret += hash[x] * i;
hash[x] = y;
++i;
}
cout << ret;
return 0;
}
4、复杂度分析
- 时间复杂度:
O(n)
,因为每个操作的平均时间复杂度为O(1)
。 - 空间复杂度:
O(n)
,用于存储哈希表。
Q123、字母异位词的长度
1、题目描述
描述
现有两个仅由小写英文字母构成的字符串s, x,请判断它们是否为字母异位词,如果是的话,输出字母异位词的长度,不是的话,返回 -1
注:如果每个字符出现的次数都相同,则称他们为字母异位词。
数据范围:
1 ≤ s.length, x.length ≤ 5 ∗ 104
示例1
输入:
"aba","aa"
返回值:
-1
说明:
第一个字符串与第二个字符串a出现的次数相同,而b出现的次数不同,不符合每个字符出现的次数都相同。故输出-1
示例2
输入:
"a","a"
返回值:
1
说明:
第一个字符串与第二个字符串每个字符出现的次数都相同,故输出相同的长度为1
2、解题思路
-
长度判断:
- 如果
s
和x
的长度不同,它们不可能为字母异位词,直接返回-1
。
- 如果
-
字符频率统计:
- 使用两个长度为
26
的数组count_s
和count_x
来分别统计s
和x
中每个小写字母的出现次数。 - 遍历字符串
s
和x
,分别统计每个字符的频率。
- 使用两个长度为
-
频率比较:
- 比较两个频率数组
count_s
和count_x
。如果所有对应位置的频率都相同,则它们是字母异位词,返回字符串的长度;否则返回-1
。
- 比较两个频率数组
3、代码实现
C++
#include <algorithm>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @param c string字符串
* @return int整型
*/
int isCongruent(string s, string c) {
// write code here
sort(s.begin(), s.end());
sort(c.begin(), c.end());
if (s != c) {
return -1;
}
return s.size();
}
};
4、复杂度分析
- 时间复杂度:
O(n)
,其中n
是字符串的长度。需要遍历两个字符串各一次,以及比较两个频率数组。 - 空间复杂度:
O(1)
,使用了两个固定大小的数组(长度为26
),与输入规模无关。
Q124、两个数组的交集
1、题目描述
描述
给定两个序列 nums1 和 nums2,返回它们的交集 。输出结果中的每个元素一定是唯一的,此外你还需要保证返回值需要按照升序排列。
示例1
输入:
[1,1,4],[5,1,4]
返回值:
[1,4]
备注:
1≤∣nums1∣,∣nums2∣≤1000 0≤nums1i,nums2i≤1000
2、代码实现
C++
#include <algorithm>
#include <unordered_set>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums1 int整型vector
* @param nums2 int整型vector
* @return int整型vector
*/
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// write code here
unordered_set<int> s1;
unordered_set<int> s2;
vector<int> ret;
for (const auto& num : nums1) {
s1.insert(num);
}
for (const auto& num : nums2) {
s2.insert(num);
}
for (const auto& num : s1) {
if (s2.count(num)) {
ret.push_back(num);
}
}
sort(ret.begin(), ret.end());
return ret;
}
};
Q125、字符串构造判定
1、题目描述
描述
给定仅由小写字母构成的字符串 S 和 T,判断是否可以从 T 中选取若干字符(每个字符最多使用一次),拼接成与 S 完全相同的字符串。
请实现函数,接收以下参数:
- 字符串 S,长度为 (1 ≦ ∣S∣ ≦ 105);
- 字符串 T,长度为 (1 ≦ ∣T∣ ≦ 105);
函数返回布尔值,表示能否从 T 构造出 S。
示例1
输入:
"wc","nowcoder"
返回值:
true
2、代码实现
C++
#include <unordered_map>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param ransomNote string字符串
* @param magazine string字符串
* @return bool布尔型
*/
bool canConstruct(string ransomNote, string magazine) {
// write code here
vector<int> v1(26);
vector<int> v2(26);
for (const auto& c : ransomNote) {
v1[c - 'a']++;
}
for (const auto& c : magazine) {
v2[c - 'a']++;
}
for (int i = 0; i < 26; ++i) {
if (v1[i] > v2[i]) {
return false;
}
}
return true;
}
};
Q126、生词篇章查询
1、题目描述
2、代码实现
C++
#include <iostream>
#include <string>
#include <set>
#include <unordered_map>
using namespace std;
int main() {
int n, m, len;
cin >> n;
string word;
unordered_map<string, set<int>> dict;
for (int i = 1; i <= n; ++i) {
cin >> len;
while (len--) {
cin >> word;
dict[word].insert(i);
}
}
cin >> m;
while (m--) {
cin >> word;
if (dict.count(word)) {
auto it = dict[word].begin();
while (it != dict[word].end()) {
cout << *it << " ";
++it;
}
}
cout << endl;
}
return 0;
}
Q127、特殊城市
1、题目描述
描述
Farmer John 有若干头奶牛。为了训练奶牛们的智力,他在谷仓的墙上放了一张美国地图。地图上标明了每个城市及其所在州的代码(前两位大写字母)。
奶牛们注意到,如果城市 A 的名称前两位字母等于城市 B 所在州代码,且城市 B 的名称前两位字母等于城市 A 所在州代码,并且 A, B 来自不同的州,则称 {A, B} 是一对特殊城市。
现有 N 座城市,请你计算共有多少对特殊城市。
输入描述:
第一行输入一个整数 N(1 ≦ N ≦ 2 × 105),表示城市数量。
接下来 N 行,每行输入两个字符串:城市名称 S(由 2∼10 个大写字母组成)和所在州代码 C(由 2 个大写字母组成),用空格隔开。保证同一州内城市名称互不相同。
输出描述:
输出一个整数,表示特殊城市对的数量。
示例1
输入:
6 MIAMI FL DALLAS TX FLINT MI CLEMSON SC BOSTON MA ORLANDO FL
输出:
1
说明:
MIAMI 位于州 FL,且 FLINT 的名称前两位字母为 FL;FLINT 位于州 MI,且 MIAMI 的名称前两位字母为 MI。其他城市不满足此条件,因此共有 1 对特殊城市。
示例2
输入:
2 ABCD EF EFGH AB
输出:
1
说明:
ABCD 位于州 EF,且 EFGH 的名称前两位字母为 EF;EFGH 位于州 AB,且 ABCD 的名称前两位字母为 AB。因此共有 1 对特殊城市。
2、代码实现
C++
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <utility>
#include <vector>
using namespace std;
int main() {
int n, ret = 0;
cin >> n;
string s, c;
unordered_map<string, unordered_map<string, int>> hash;
while (n--) {
cin >> s >> c;
string prefix = s.substr(0, 2);
if (prefix == c) {
continue;
}
hash[prefix][c]++;
ret += hash[c][prefix];
}
cout << ret;
return 0;
}
Q128、哈希冲突
1、题目描述
描述
在密码学中,哈希碰撞是指找到两个不同的输入,使它们经过同一个哈希函数处理后得到相同的输出。
在代码模板中给定了哈希函数 H,其计算过程如下:
- 接收输入字符串 enc,先与给定密钥 key 右拼接,得到字符串 key + enc;
- 对拼接后的字符串计算 SHA256 哈希值;
- 取 SHA256 哈希值的前 ∣enc∣ 个字符作为输出结果,其中 ∣enc∣ 是 enc 的长度。
给定密钥 key (1 ≦ ∣key∣ ≦ 10)和目标长度 L(1 ≦ L ≦ 5)(作为全局变量而不是参数提供,你可以直接使用这两个变量名来获取对应的值),请找到两个不同的由小写字母组成的字符串 s1 和 s2,它们的长度均为 L 且满足
H(s1) = H(s2)
请将这两个字符串 s1 和 s2 组成值对作为函数的返回值。如果有多种可能的字符串值对,你可以输出任意一个符合题意的值对。
输入描述:
输入数据仅用于主函数获取数据后调用你实现的函数,你其实可以不用管。
第一行输入一个整数 L(1 ≦ L ≦ 5),表示待构造字符串的长度。
第二行输入一个由小写字母组成的字符串 key,长度满足 1 ≦ ∣key∣ ≦ 10。
输出描述:
输出数据仅用于主函数获取返回值后于评测机交互,你其实可以不用管。
输出两个由小写字母组成的字符串 s1 和 s2,长度均为 L 且 s1 ≠ s2,它们满足 H(s1) = H(s2),中间用一个空格分隔。
示例1
输入:
2 a
输出:
mq ap
2、代码实现
Python3
import hashlib
from typing import Tuple
import random
import string
def sha256(text: str) -> str:
return hashlib.sha256(text.encode()).hexdigest()
enc_len = 0
enc_key = ""
def H(s: str) -> str:
combined = enc_key + s
return sha256(combined)[:enc_len]
def solve() -> Tuple[str, str]:
mp = {}
while True:
s = ''.join(random.choice(string.ascii_lowercase) for _ in range(enc_len))
h = H(s)
if h in mp:
if mp[h] != s:
return mp[h], s
else:
mp[h] = s
def main():
global enc_len, enc_key
enc_len = int(input())
enc_key = input()
result = solve()
# print(f"{H(result[0])}{H(result[1])}")
print(f"{result[0]} {result[1]}")
if __name__ == "__main__":
main()
Q129、玩家积分榜系统
1、题目描述
描述
你正在为一个火爆的网络游戏设计玩家积分榜系统。系统启动时,积分榜是空的。
服务器需要处理来自游戏客户端的各种操作请求,以维护每个玩家的最高积分记录。
具体而言,操作请求分为以下四种类型:
- 更新或插入积分:参数为一个字符串 NAME 和一个正整数 SCORE,如果名为 NAME 的玩家已存在,则将其积分更新为 SCORE;否则新建一个名为 NAME,积分为 SCORE 的新玩家。没有返回值。
- 查询积分:参数为一个字符串 NAME。如果名为 NAME 的玩家已存在,输出其当前积分;否则输出
Not found
。没有返回值。 - 删除玩家记录:参数为一个字符串 NAME。如果名为 NAME 的玩家已存在,则删除该玩家的积分记录,输出
Deleted successfully
;否则输出Not found
。没有返回值。 - 统计玩家总数:没有参数。输出当前积分榜中玩家的总数量。没有返回值。
注意,需要实现的所有函数均没有返回值,操作的结果(如果有的话)需要直接进行标准输出!
输入描述:
输入数据仅用于主函数获取数据后调用你实现的函数,你其实可以不用管。
第一行输入一个整数 Q(1 ≦ Q ≦ 105),表示需要处理的操作总数。
接下来 Q 行,每行输入一个操作,分为以下四种类型:
-
更新或插入积分:格式为
1 NAME SCORE
,其中NAME
为由字母和数字组成、区分大小写、长度不超过 20 的字符串,SCORE
为正整数(0 < SCORE < 231)。如果NAME
已存在,则更新其积分;否则插入新玩家。 -
查询积分:格式为
2 NAME
。如果玩家NAME
存在,输出其当前积分;否则输出Not found
。 -
删除玩家记录:格式为
3 NAME
。如果玩家NAME
存在并删除成功,输出Deleted successfully
;否则输出Not found
。 -
统计玩家总数:格式为
4
。输出当前积分榜中玩家的总数量。
输出描述:
输出数据仅用于主函数获取返回值后于评测机交互,你其实可以不用管。
针对每个操作,输出对应结果,每个结果占一行:
- 操作类型 1:输出
OK
。 - 操作类型 2:输出玩家积分或
Not found
。 - 操作类型 3:输出
Deleted successfully
或Not found
。 - 操作类型 4:输出当前玩家总数。
示例1
输入:
5 1 wangzai 10 2 wangzai 3 wangzai 2 wangzai 4
输出:
OK 10 Deleted successfully Not found 0
说明:
操作 1:插入玩家 `wangzai` 积分 10,输出 `OK`; 操作 2:查询 `wangzai`,输出 `10`; 操作 3:删除 `wangzai`,输出 `Deleted successfully`; 操作 4:查询 `wangzai`,未找到,输出 `Not found`; 操作 5:统计玩家总数,当前无玩家,输出 `0`。
2、解题思路
-
数据结构选择:
- 使用哈希表(如
unordered_map
)来存储玩家名称和积分,因为哈希表支持高效的插入、查找和删除操作。
- 使用哈希表(如
-
操作实现:
- 插入或更新积分:直接更新哈希表中的玩家积分。
- 查询积分:查找哈希表,存在则输出积分,否则输出
Not found
。 - 删除玩家:从哈希表中删除玩家,存在则输出删除成功,否则输出
Not found
。 - 统计玩家总数:输出哈希表的大小。
-
性能优化:
- 输入输出优化:使用
ios_base::sync_with_stdio(false)
和cin.tie(NULL)
加速输入输出。 - 哈希表操作的时间复杂度为平均
O(1)
,可以高效处理大量操作。
- 输入输出优化:使用
3、代码实现
C++
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
unordered_map<string, int> playerScores;
void insertOrUpdateScore(const string& name, int score) {
// TODO: 实现插入或更新逻辑
playerScores[name] = score;
cout << "OK" << endl;
}
void queryScore(const string& name) {
// TODO: 实现查询逻辑
if (playerScores.count(name)) {
cout << playerScores[name] << endl;
} else {
cout << "Not found" << endl;
}
}
void deletePlayer(const string& name) {
// TODO: 实现删除逻辑
if (playerScores.count(name)) {
playerScores.erase(name);
cout << "Deleted successfully" << endl;
} else {
cout << "Not found" << endl;
}
}
void countPlayers() {
// TODO: 实现统计逻辑
cout << playerScores.size() << endl;
}
int main() {
// 提高输入输出效率 (可选)
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int q;
cin >> q;
while (q--) {
int op;
cin >> op;
if (op == 1) {
string name;
int score;
cin >> name >> score;
insertOrUpdateScore(name, score);
} else if (op == 2) {
string name;
cin >> name;
queryScore(name);
} else if (op == 3) {
string name;
cin >> name;
deletePlayer(name);
} else if (op == 4) {
countPlayers();
}
}
return 0;
}
4、复杂度分析
- 时间复杂度:
- 插入、查询、删除操作的平均时间复杂度为
O(1)
,最坏情况为O(n)
(哈希冲突时)。 - 统计操作的时间复杂度为
O(1)
。 - 总体时间复杂度为
O(Q)
,其中Q
是操作数量。
- 插入、查询、删除操作的平均时间复杂度为
- 空间复杂度:
O(N)
,其中N
是玩家数量。
Q130、贪吃蛇游戏
1、题目描述
描述
在本题中,我们使用一个全局变量双端队列 q 来模拟贪吃蛇的身体。队列从头到尾存放蛇身各部分的坐标,队头为蛇尾,队尾为蛇头。
系统会调用以下两个函数,你需要实现它们的逻辑:
-
moveSnack 函数:moveSnack 函数接受一个方向参数 dir,dir ∈ {1, 2, 3, 4},分别表示上下左右移动。函数功能如下:
- 将蛇头向对应方向移动一个单位长度;
- 身体的其他部分依次跟随前移一格;
- 如果移动后蛇头与身体其他部分重叠,则函数返回 true,表示会撞到自己,此时不执行任何移动操作;否则返回 false。
-
eatSnack 函数:eatSnack 函数接受一个方向参数 dir,dir ∈ {1, 2, 3, 4},分别表示上下左右移动。函数功能如下:
- 将蛇头向对应方向移动一个单位长度;
- 身体的其他部分依次跟随前移一格;
- 蛇尾在原方向上生长一个单位长度;
- 如果移动后蛇头与身体其他部分重叠,则函数返回 true,表示会撞到自己,此时不执行任何移动操作;否则返回 false。
输入描述:
输入数据仅用于主函数获取数据后调用你实现的函数,你其实可以不用管。
第一行输入两个正整数 n, q (1 ≦ n, q ≦ 103),分别表示初始蛇身长度和操作次数。
接下来 n 行,每行输入两个整数 xi, yi (−104 ≦ xi, yi ≦ 104),表示蛇身从尾部到头部第 i 节的坐标。
随后 q 行,每行输入两个整数 op, dir (1 ≦ op ≦ 2; 1 ≦ dir ≦ 4):
- 当 op=1 时,调用 moveSnack 函数;
- 当 op=2 时,调用 eatSnack 函数。
输出描述:
输出数据仅用于主函数获取返回值后于评测机交互,你其实可以不用管。
对每次操作进行如下处理:
- 如果会撞到自己,输出 −1 并停止程序;
- 否则,输出从蛇头到蛇尾各节的坐标,每对坐标占一行。
示例1
输入:
6 5 0 -1 0 0 0 1 1 1 2 1 2 2 1 1 1 3 1 2 1 2 1 1
输出:
2 3 2 2 2 1 1 1 0 1 0 0 1 3 2 3 2 2 2 1 1 1 0 1 1 2 1 3 2 3 2 2 2 1 1 1 1 1 1 2 1 3 2 3 2 2 2 1 -1
2、解题思路
-
数据结构:
- 使用双端队列
deque
来存储蛇身的坐标,队头为蛇尾,队尾为蛇头。
- 使用双端队列
-
移动逻辑:
- 根据方向
dir
计算蛇头的新位置。 - 检查新位置是否与蛇身其他部分重叠。
- 如果不重叠,移除队头(蛇尾),加入新蛇头到队尾;否则返回
true
。
- 根据方向
-
吃果子逻辑:
- 类似移动逻辑,但不移除队头,直接在队尾添加新蛇头,并在队头添加一个反向移动的蛇尾延长部分。
- 同样检查碰撞。
-
方向处理:
- 定义方向数组
dx
和dy
对应上下左右的移动。
- 定义方向数组
3、代码实现
C++
#include <bits/stdc++.h>
#include <string>
#include <unordered_set>
#include <utility>
using namespace std;
using ll = long long;
deque<pair<int, int>> snake;
unordered_set<string> paths;
int dx[] = {0, 0, 0, -1, 1}; // 占位 上 下 左 右
int dy[] = {0, 1, -1, 0, 0};
bool moveSnake(int dir) {
// TODO: 请实现移动逻辑
auto head = snake.back();
int x = head.first + dx[dir];
int y = head.second + dy[dir];
// 检查是否撞到自己
string s = to_string(x) + ',' + to_string(y);
if (paths.count(s)) {
return true;
}
// 移动蛇身
auto tail = snake.front();
snake.pop_front();
snake.emplace_back(x, y);
// 更新路径
paths.erase(to_string(tail.first) + ',' + to_string(tail.second));
paths.insert(s);
return false;
}
bool eatSnake(int dir) {
// TODO: 请实现吃果子生长逻辑
auto head = snake.back();
int x = head.first + dx[dir];
int y = head.second + dy[dir];
// 检查是否撞到自己
string s = to_string(x) + ',' + to_string(y);
if (paths.count(s)) {
return true;
}
// 移动蛇身
snake.emplace_back(x, y);
// 更新路径
paths.insert(s);
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
snake.clear();
paths.clear();
for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
snake.emplace_back(x, y);
paths.insert(to_string(x) + ',' + to_string(y));
}
for (int i = 0; i < q; i++) {
int op, dir;
cin >> op >> dir;
bool collision = (op == 1 ? moveSnake(dir) : eatSnake(dir));
if (collision) {
cout << -1 << '\n';
return 0;
} else {
for (auto it = snake.rbegin(); it != snake.rend(); ++it) {
cout << it->first << ' ' << it->second << '\n';
}
}
}
return 0;
}