牛客 《新手入门 130》 整整 130 题量大管饱题解套餐

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、题目描述

QQ_1749391355697

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、题目描述

QQ_1749393052438

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、题目描述

QQ_1749393977169

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、题目描述

QQ_1749394037309

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、题目描述

QQ_1749394089247

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、题目描述

QQ_1749394399481

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、题目描述

QQ_1749439957717

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、题目描述

QQ_1749440007015

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、题目描述

image-20250609114142845

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、题目描述

QQ_1749440556960

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)来模拟整个过程:

  1. 将所有人按顺序放入队列
  2. 先让队列前k个人出队并重新入队,使编号k的人位于队首
  3. 然后开始报数:每次让 m-1个人出队并重新入队
  4. 第m个人直接出队(不重新入队)
  5. 重复这个过程直到队列只剩一人
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、题目描述

QQ_1749443689394

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、题目描述
描述

牛牛和牛妹正在玩一个博弈游戏。每人可以选择一个棋子:elephanttigercatmouse
它们之间的胜负规则如下:

  • elephanttiger
  • tigercat
  • catmouse
  • mouseelephant

如果一方的棋子能够吃掉另一方的棋子,则该方获胜;否则为平局。
给定牛牛和牛妹所出的棋子,请判断比赛结果。

输入描述:

在一行输入两个以空格分隔的字符串 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位数(1-9):9个数字,共9位
    • 2位数(10-99):90个数字,共180位
    • 3位数(100-999):900个数字,共2700位
    • 依此类推…
  2. 定位步骤
    • 确定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、解题思路
  1. 将整数转换为字符串:方便逐位处理
  2. 从右向左遍历:每三位插入一个逗号
  3. 处理边界情况:避免在最左侧添加多余的逗号
  4. 构建结果字符串:将所有字符和逗号组合
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、解题思路
  1. 公式推导:直接从给定的物理公式出发,推导第一宇宙速度的表达式。
  2. 数值计算
    • 使用题目给定的万有引力常量 G = 6.67×10−11
    • 计算 G × M / r
    • 最后取平方根得到第一宇宙速度
  3. 浮点数处理:注意数值精度和可能的浮点误差。
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. 模拟移动过程

    • 初始位置为 1(即 current=1)。
    • 每次移动时,逆时针走 acurrent 个小朋友。
    • 因为小朋友是围成一圈的,所以移动时需要处理环形结构,可以用模运算来定位。
  2. 环形处理

    • 每次移动 acurrent 步,相当于逆时针方向移动,即相当于顺时针方向移动 n - acurrent 步。

    • 当前位置的计算公式:

      current = (current − acurrent − 1) mod  n+1

      • 这里 current − acurrent − 1 是为了处理逆时针移动的偏移,然后通过模 n 和 +1 来调整到 1∼n 的范围。
  3. 复杂度优化

    • 直接模拟每次移动的复杂度是 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、解题思路
  1. 提取括号子序列

    • 遍历字符串 S,筛选出所有 [, ], (, ) 字符,组成一个新的子序列。
  2. 检查子序列的合法性

    • 使用栈数据结构来检查括号的匹配。
    • 遍历子序列,遇到左括号 ([ 时入栈。
    • 遇到右括号 )] 时,检查栈顶的左括号是否与之匹配:
      • ) 必须匹配 (
      • ] 必须匹配 [
    • 如果匹配,弹出栈顶的左括号;否则,序列不合法。
    • 遍历结束后,如果栈为空,则序列合法;否则不合法。
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”。

牛牛称一个字符串为好串,当且仅当它可以通过若干次上述操作从空串生成。

例如,abaabbaababb 都是好串,而 aabbaabbb 不是好串。

现给定一个字符串 s,请判断 s 是否是好串。

输入描述:

在一行中输入一个字符串 s,仅由小写字母组成,长度满足 1 ≦ ∣s∣ ≦ 105

输出描述:

如果 s 是好串,输出 Good;否则输出 Bad

示例1

输入:

ab

输出:

Good

说明:

初始空串,插入一次 "ab" 即可得到 "ab"。
示例2

输入:

aab

输出:

Bad

说明:

无法通过插入 "ab" 操作得到 "aab"。
示例3

输入:

abaababababbaabbaaaabaababaabbabaaabbbbbbbb

输出:

Bad
2、解题思路

关键观察

  1. 字符顺序:插入 "ab" 时,总是插入 'a''b' 这对字符,所以最终字符串中 'a''b' 的数量必须相等。
  2. 前缀条件:在任何时刻,'a' 的数量必须不少于 'b' 的数量,否则无法通过插入 "ab" 生成这样的字符串(因为每次插入都是先 'a''b')。
  3. 构建过程:可以通过逆向思维,从字符串 s 逐步删除 "ab" 子串,如果能删到空字符串,则 s 是好串。

具体步骤

  1. 检查字符串 s'a''b' 的数量是否相等。如果不相等,直接返回 Bad
  2. 使用栈来模拟删除 "ab" 的过程:
    • 遍历字符串 s,遇到 'a' 就入栈。
    • 遇到 'b' 时,检查栈顶是否为 'a'
      • 如果是,弹出 'a',表示删除一个 "ab" 子串。
      • 如果不是(栈为空或栈顶不是 'a'),则无法删除,返回 Bad
  3. 遍历结束后,如果栈为空,则所有 "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、解题思路

关键观察

  • 泡泡的变化是自左至右依次进行的,因此每次变化会影响后续的扫描。
  • 每次变化后,新的泡泡可能会触发更多的变化(例如融合后产生的大泡泡可能与相邻的大泡泡爆炸)。
  • 这类似于括号匹配问题,可以用栈来处理。

具体步骤

  1. 使用栈来模拟泡泡的变化过程:
    • 遍历字符串 s,依次将泡泡压入栈。
    • 每次压入后,检查栈顶的两个泡泡是否满足变化条件:
      • 栈顶两个 'o' 融合为 'O'(弹出两个 'o',压入 'O')。
      • 栈顶两个 'O' 爆炸(弹出两个 'O')。
    • 重复检查直到栈顶不再满足变化条件。
  2. 栈中剩余的泡泡即为最终结果。
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、解题思路

关键观察

  1. 运算符优先级:括号 () 的优先级最高,其次是乘法 *,最后是加减法 +-
  2. 运算顺序:同一优先级的运算符从左到右计算(加减法)或直接计算(乘法)。
  3. 括号处理:括号内的表达式需要优先计算,可以用递归或栈来处理。

方法选择

  • 双栈法:使用两个栈,一个存储数字(nums),一个存储运算符(ops),按照优先级顺序进行计算。
  • 递归下降法:通过递归的方式处理括号内的表达式,适合处理嵌套括号。

这里选择双栈法,因为它更直观且时间复杂度为 O(n)。

具体步骤

  1. 初始化两个栈:nums(数字)和 ops(运算符)。
  2. 遍历字符串 s
    • 遇到数字:提取完整的数字并压入 nums
    • 遇到 (:压入 ops
    • 遇到 ):计算 ops 栈顶到 ( 的所有运算符。
    • 遇到运算符 +-*
      • 若当前运算符优先级 ≤ ops 栈顶运算符优先级,则先计算栈顶运算符。
      • 压入当前运算符。
  3. 遍历结束后,计算 ops 剩余的所有运算符。
  4. 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、解题思路

关键观察

  1. 后缀表达式计算规则
    • 遇到操作数时,将其压入栈。
    • 遇到运算符时,弹出栈顶的两个操作数,进行计算后将结果压入栈。
    • 最终栈顶即为表达式结果。
  2. 操作数提取
    • 由于操作数由 '#' 分隔,需要逐个字符读取直到 '#',拼接成完整的数字。

具体步骤

  1. 初始化一个栈 nums 用于存储操作数。
  2. 遍历字符串 s
    • 如果是数字,提取完整的数字(直到 '#')并压入 nums
    • 如果是运算符,弹出栈顶的两个操作数,计算后将结果压入栈。
  3. 最终 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),表示测试组数。
接下来对于每组测试,依次输入:

  1. 一行整数 n (1 ≦ n ≦ 105),表示序列长度;
  2. 一行 n 个整数,为入栈序列 pushed1, … , pushedn
  3. 一行 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、解题思路

关键观察

  1. 栈的操作规则
    • 只能从栈顶出栈。
    • 出栈顺序必须与 popped 的顺序一致。
  2. 模拟过程
    • 遍历 pushed 序列,逐个入栈。
    • 每次入栈后,检查栈顶是否与 popped 的当前元素相同:
      • 如果相同,则出栈,并移动到 popped 的下一个元素。
      • 继续检查栈顶,直到不匹配为止。
    • 最终,如果栈为空且 popped 的所有元素都匹配,则为合法序列。

具体步骤

  1. 初始化一个栈 st 和一个指针 i 指向 popped 的第一个元素。

  2. 遍历 pushed 序列:

    • 将当前元素入栈。
    • 循环检查栈顶是否等于 popped[i]
      • 如果相等,出栈并 i++
  3. 遍历结束后,检查栈是否为空:

    • 如果为空,则 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、解题思路

关键观察

  1. 字典序最大的序列:在每一步选择出栈时,尽可能出栈当前最大的元素。
  2. 栈的性质:只能从栈顶出栈,出栈顺序受入栈顺序限制。

具体步骤

  1. 初始化:使用一个栈 st 和一个指针 target 指向当前期望的最大值 n
  2. 遍历 P 序列
    • 将当前元素 P[i] 入栈。
    • 检查栈顶元素是否等于 target
      • 如果相等,出栈并加入输出序列,同时 target--
      • 继续检查栈顶,直到不匹配为止。
  3. 处理剩余元素:将栈中剩余元素依次出栈并加入输出序列。

优化

  • 使用 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 行,每行描述一个操作,格式如上所述。

输出描述:

对于每个操作 234,按执行顺序,每行输出对应的结果。

示例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、解题思路
  1. 选择数据结构

    • 使用 std::queuestd::deque 模拟队列操作。
    • std::queue 是一个适配器,底层默认使用 std::deque,适合先进先出(FIFO)的操作。
  2. 处理操作

    • 遍历每个操作:
      • 操作 1 x:直接 push 到队列。
      • 操作 2:检查队列是否为空,非空则 pop 并输出队首;否则输出 -1
      • 操作 3:检查队列是否为空,非空则输出 front;否则输出 -1
      • 操作 4:输出队列的 size
  3. 注意事项

    • 输入规模较大(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、解题思路
  1. 模拟过程

    • 使用队列来模拟学生的排队顺序。
    • 使用栈来模拟三明治的发放顺序(栈顶先发)。
    • 循环执行匹配操作,直到无法继续匹配。
  2. 终止条件

    • 当队列中没有一个学生能匹配当前栈顶三明治时,停止循环。
    • 此时队列中剩余的学生即为无法拿到三明治的学生人数。
  3. 优化

    • 在每次循环中,如果队首学生和栈顶三明治匹配,直接出队和出栈。
    • 如果不匹配,将队首学生移至队尾。
    • 需要记录当前队列的长度,避免无限循环(比如所有学生都不匹配栈顶三明治)。
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、解题思路
  1. 模拟队列操作

    • 使用队列存储当前需要处理的下标。
    • 每次取出队首下标 i,耗时 1 秒。
    • 如果 a[i] > 1,则 a[i]1,并将 i 重新加入队尾。
    • 如果 a[i] == 1,则移除 i,并检查是否为 k,如果是则返回总耗时。
  2. 终止条件

    • 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 的顺序循环行动。若剩余有行动权的参议员均属于同一阵营,则该阵营获胜并结束流程;否则,当前参议员可选择:

  1. 弹劾一名仍可行动的参议员,使其在后续轮次中失去行动权。
  2. (仅当剩余参议员同阵营时)直接宣布胜利。

你需要实现一个函数,求出所有参议员均采用最优策略时,最终获胜的阵营的名称。

函数接受的参数为一个字符串 s,长度 n (1 ≦ n ≦ 104),且 si ∈ {R, D}。
函数的返回值为一个字符串 RedDark,分别表示红帮或黑帮获胜。

示例1

输入:

"DR"

返回值:

"Dark"

说明:

第 1 轮时,第一个参议员来自 Dark 阵营,他可以使用第一项权利禁止第二个参议员的权利
这样第二个参议员就无法使用任何权利了。
第 2 轮时,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人。
示例2

输入:

"DRR"

返回值:

"Red"

说明:

第 1 轮时,第一个来自 Dark 阵营的参议员可以使用第一项权利禁止第二个参议员的权利
第 2 轮时,第三个来自 Red 阵营的参议员可以使用他的第一项权利禁止第一个参议员的权利
这样在第 3 轮只剩下第三个参议员拥有投票的权利,于是他可以宣布胜利。
2、解题思路
  1. 模拟投票过程

    • 使用两个队列分别存储 R 和 D 阵营的参议员索引。
    • 每次从队列头部取出一个参议员,弹劾对方阵营的一个参议员(从对方队列头部取出)。
    • 被弹劾的参议员失去行动权,未被弹劾的参议员重新加入队列尾部(因为下一轮会再次行动)。
    • 循环直到某一阵营的队列为空。
  2. 最优策略

    • 每个参议员都会优先弹劾对方阵营中最早行动的参议员(即队列头部),因为这样可以尽快削弱对方阵营。
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. 模拟缓存操作

    • 使用一个队列(或列表)来模拟缓存,队列头部是最早加入的单词,尾部是最新加入的单词。
    • 遍历文章中的每个单词:
      • 如果单词在缓存中,跳过(缓存命中)。
      • 如果不在缓存中(缓存未命中),计数器加 1:
        • 如果缓存未满,直接加入队列尾部。
        • 如果缓存已满,移除队列头部,加入队列尾部。
  2. 数据结构选择

    • 使用 unordered_set 快速判断单词是否在缓存中。
    • 使用 queuedeque 记录缓存中单词的顺序。
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_setqueue 操作均为 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、解题思路
  1. 数据结构选择

    • 需要高效支持插入、删除、查找前驱和后继操作。
    • std::setstd::unordered_set 可以高效处理插入、删除和存在性检查。
    • std::unordered_set 不支持有序操作(前驱和后继),因此选择 std::set(基于红黑树实现,有序且操作高效)。
  2. 前驱和后继查询

    • std::set 提供 lower_boundupper_bound 方法,可以高效找到前驱和后继。
  3. 具体操作

    • 插入:使用 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_boundupper_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)。

  1. 插入操作:直接调用 multiset.insert(x)
  2. 删除操作:使用 multiset.erase(multiset.find(x)) 删除一个 x(若存在)。
  3. 计数操作multiset.count(x) 可以高效统计 x 的个数。
  4. 总元素个数multiset.size() 返回所有元素的数量(含重复)。
  5. 前驱查询:使用 lower_bound(x) 找到第一个不小于 x 的元素,前一个元素即为前驱。
  6. 后继查询:使用 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、解题思路
  1. 数据结构选择

    • 插入和查找是否存在可以高效使用 std::setstd::unordered_set,但需要有序操作来支持提取。
    • 提取操作需要找到与 x 最接近的元素,std::set 提供的有序性和 lower_bound 操作非常适合。
  2. 提取操作实现

    • 使用 lower_bound(x) 找到第一个不小于 x 的元素 it
    • 可能的候选元素是 itprev(it)(如果存在)。
    • 计算这两个候选与 x 的绝对差,选择较小的(若相等,选择较小的元素)。
    • 删除并输出选中的元素。
  3. 边界情况

    • 如果集合为空,直接输出 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_bounderase 均为对数时间。
  • 空间复杂度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、解题思路
  1. 问题转化

    • 我们需要找到满足 a_i = a_j + C 的有序数对 (i, j)
    • 这相当于统计每个元素 a_j,有多少元素 a_i 等于 a_j + C
  2. 高效统计

    • 可以使用哈希表(如 unordered_map)来记录每个元素出现的次数。
    • 对于每个元素 a_j,查询哈希表中 a_j + C 出现的次数,并累加到总对数中。
  3. 特殊情况处理

    • C = 0 时,需要统计每个元素的出现次数,然后对于每个元素 a_j,对数为其出现次数的平方(因为 ij 可以相同)。
    • 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. 快乐数的定义

    • 每次将数字替换为其各位数字的平方和。
    • 如果能变为1,则是快乐数;如果进入无限循环(检测到重复出现的数字),则不是快乐数。
  2. 关键观察

    • 如果一个数字不是快乐数,它会进入一个循环,例如4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4。
    • 因此,可以通过检测是否进入这个循环来判断是否不是快乐数。
  3. 实现方法

    • 使用一个哈希集合(如 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、解题思路
  1. 问题分析

    • 字符串 J 中的字符都是不重复的宝石类型。
    • 字符串 S 中的字符是手中石头序列,我们需要统计其中有多少个字符存在于 J 中。
  2. 实现方法

    • J 中的字符存储在一个集合中,以方便快速查找。
    • 遍历 S 中的每个字符,检查其是否存在于 J 的集合中。
    • 统计满足条件的字符数量。
  3. 优化

    • 使用哈希集合(如 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 的长度。遍历 JS 各一次。
  • 空间复杂度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 组数据,每组数据格式如下:

  1. 第一行包含整数 n (1 ≤ n ≤ 5 × 104);

  2. 第二行包含 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、解题思路
  1. 问题分析

    • 遍历序列 a 中的每个元素,检查其是否在之前的元素中出现过。
    • 如果未出现过,则将其加入序列 b;否则跳过。
  2. 实现方法

    • 使用一个哈希集合(如 unordered_set)来记录已经出现过的元素。
    • 遍历序列 a,对于每个元素,检查是否在集合中。
    • 如果不在集合中,将其加入集合和序列 b;否则跳过。
  3. 优化

    • 使用 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、解题思路
  1. 输入处理

    • 读取学生人数N。
    • 读取N行数据,每行包含学生姓名和三门成绩。
  2. 计算总分

    • 对于每个学生,计算其总分T = c1 + c2 + c3。
  3. 比较总分

    • 遍历所有学生,记录当前最高总分和对应学生的信息。
    • 如果遇到总分更高的学生,更新最高总分和学生信息。
    • 如果总分相同,保留输入顺序靠前的学生。
  4. 输出结果

    • 输出总分最高的学生的姓名和三门成绩。
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、解题思路
  1. 欧几里得距离公式:

    • 两点 (x1, y1)(x2, y2) 之间的距离 d 计算公式为:

      d = sqrt((x2 − x1)2 + (y2 − y1)2)

  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、解题思路
  1. 直线方程

    • 直线可以由两点 (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 Ax+By+C=0,其中:

      A = y2 − y1, B = x1 − x2, C = x2 ⋅ y1 − x1 ⋅ y2

  2. 点到直线的距离公式

    • (a, b) 到直线 A ⋅ x + B ⋅ y + C = 0 A \cdot x + B \cdot y + C = 0 Ax+By+C=0 的距离 d 为:

      d = ∣Aa+Bb+C∣ / sqrt(A2 + B2)

  3. 实现步骤

    • 解析输入的点 (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、解题思路
  1. 海伦公式

    • 计算三角形三边的长度 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(sa)(sb)(sc))
  2. 向量叉乘法

    • 向量 A B = ( x 2 − x 1 , y 2 − y 1 ) AB = (x2 - x1, y2 - y1) AB=(x2x1,y2y1) A C = ( x 3 − x 1 , y 3 − y 1 ) AC = (x3 - x1, y3 - y1) AC=(x3x1,y3y1)
    • 叉积 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=(x2x1)(y3y1)(y2y1)(x3x1)
    • 面积 A = ∣ A B × A C ∣ / 2 A = |AB × AC| / 2 A=AB×AC/2
  3. 实现步骤

    • 读取三个点的坐标。
    • 使用向量叉乘法计算面积。
    • 输出结果,保留两位小数。
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 = ∣Aa+Bb+C∣ / sqrt(A2 + B2)

  1. 两点间距离

    • 如果 d < r,则直线与圆相交于两点,两点间的距离为:∣AB∣ = 2 ⋅ (r2 − d2)
    • 如果 d == r,则直线与圆相切,两点重合,距离为 0
  2. 实现步骤

    • 计算直线方程的参数 A, B, C
    • 计算圆心到直线的距离 d
    • 根据 dr 的关系计算两点间的距离。
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、解题思路
  1. 直线方程

    • 直线可以由两点 (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 Ax+By+C=0,其中:

      A = y2 − y1, B = x1 − x2, C = x2 ⋅ y1 − x1 ⋅ y2

  2. 交点计算

    • 两条直线 A1x + B1y + C1 = 0A2x + B2y + C2 = 0 的交点 (x, y) 可以通过解线性方程组得到:

    • 如果分母 A 1 ⋅ B 2 − A 2 ⋅ B 1 A1 \cdot B2 - A2 \cdot B1 A1B2A2B1 为 0,则两条直线平行或重合,无交点。

  3. 实现步骤

    • 计算两条直线的方程参数 A1, B1, C1A2, B2, C2
    • 计算分母 D = A 1 ⋅ B 2 − A 2 ⋅ B 1 D = A1 \cdot B2 - A2 \cdot B1 D=A1B2A2B1
    • 如果 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,支持以下操作:

  1. 插入操作:给定整数 x,将 x 加入集合 S;

  2. 查询操作:输出 S 中的最小元素;

  3. 删除操作:删除 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、解题思路
  1. 数据结构选择

    • 需要高效地插入、查询和删除最小元素,可以使用 std::multiset,因为它自动维护元素的排序并且允许重复元素。
    • 另一种方法是使用 std::priority_queue,但它不支持直接删除最小元素(除非使用特定的技巧)。
  2. 操作实现

    • 插入操作:直接将 x 插入 multiset
    • 查询操作:输出 multiset 的第一个元素(即最小元素)。
    • 删除操作:删除 multiset 的第一个元素。
  3. 输入输出处理

    • 根据 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;
  • 若操作类型为 23,则形式为单个整数 23
输出描述:

对于每次操作 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、解题思路
  1. 数据结构选择

    • 需要高效地插入、查询和删除字典序最小的字符串,可以使用 std::multisetstd::priority_queue
    • 本题选用 std::priority_queue 并自定义比较函数,使其维护字典序最小的字符串在堆顶。
  2. 比较逻辑

    • 使用 std::greater<string> 作为比较函数,确保堆顶是字典序最小的字符串。
  3. 操作实现

    • 插入操作:直接将字符串插入优先队列。
    • 查询操作:返回堆顶字符串。
    • 删除操作:删除堆顶字符串。
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、解题思路
  1. 数据结构选择

    • 需要高效地插入、查询和删除最小值和最大值,可以使用 std::multiset,因为它自动维护元素的排序并且允许重复元素。
    • 另一种方法是使用两个 std::priority_queue,一个维护最小值(小顶堆),一个维护最大值(大顶堆),但这样删除操作会比较复杂。
  2. 操作实现

    • 插入操作:直接将 x 插入 multiset
    • 查询最小值:输出 multiset 的第一个元素(即最小元素)。
    • 查询最大值:输出 multiset 的最后一个元素(即最大元素)。
    • 删除最小值:删除 multiset 的第一个元素。
    • 删除最大值:删除 multiset 的最后一个元素。
  3. 输入输出处理

    • 根据 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、解题思路
  1. 数据结构选择

    • 频繁的插入和删除操作需要使用高效的链表结构。std::list 在插入和删除操作上的时间复杂度为 O(1)
    • 需要快速查找特定顾客的位置,可以使用哈希表(std::unordered_map)来存储顾客名字到链表迭代器的映射。
  2. 操作实现

    • 初始化时将顾客顺序存入链表,并记录每个顾客的迭代器。
    • 对于每次插队事件:
      • 使用哈希表找到 x_iy_i 的迭代器。
      • 从链表中移除 x_i
      • y_i 之前插入 x_i
      • 更新哈希表中 x_i 的迭代器。
  3. 输出结果

    • 遍历链表,输出最终的顾客顺序。
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、解题思路
  1. 暴力法
    • 最简单的方法是使用双重循环遍历所有可能的 (i, j) 组合,检查是否满足 a[i] + a[j] == T。时间复杂度为 O(n^2),在 n 较大时可能不够高效。
  2. 哈希表法
    • 使用哈希表(或字典)来存储已经遍历过的元素及其下标。
    • 对于每个元素 a[j],检查 T - a[j] 是否存在于哈希表中。如果存在,则返回对应的下标 ij。时间复杂度为 O(n)
  3. 双指针法
    • 如果序列是有序的,可以使用双指针法,一个指针从头部开始,另一个从尾部开始,向中间移动,寻找满足 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、解题思路
  1. 数据结构选择

    • 使用哈希集合(std::unordered_set)来存储字符串,因为集合会自动去重。
    • 遍历所有字符串,将每个字符串插入到集合中,集合会自动处理重复的字符串。
  2. 算法选择

    • 遍历每个字符串,插入到集合中。
    • 最终集合的大小即为不同字符串的数量。
  3. 复杂度分析

    • 插入操作的平均时间复杂度为 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、解题思路
  1. 数据结构选择

    • 由于 x 的范围很大(0 ≤ x < 2^64),直接使用数组存储不可行。
    • 使用哈希表(如 std::unordered_map)来存储 f(x) 的当前值,初始时所有 x 对应的值为 0
    • 哈希表的插入和查询操作的平均时间复杂度为 O(1),适合处理大规模数据。
  2. 算法流程

    • 初始化一个空的哈希表 f
    • 遍历每个操作 (x, y)
      • 查询当前 f[x] 的值作为 ans_i
      • 更新 f[x] = y
      • 累加 i * ans_i 到总和中。
    • 最终总和自动对 2^64 取模(因为使用 unsigned long long 类型存储)。
  3. 注意事项

    • 由于 xy 的范围是 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、解题思路
  1. 长度判断

    • 如果 sx 的长度不同,它们不可能为字母异位词,直接返回 -1
  2. 字符频率统计

    • 使用两个长度为 26 的数组 count_scount_x 来分别统计 sx 中每个小写字母的出现次数。
    • 遍历字符串 sx,分别统计每个字符的频率。
  3. 频率比较

    • 比较两个频率数组 count_scount_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 successfullyNot 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、解题思路
  1. 数据结构选择

    • 使用哈希表(如 unordered_map)来存储玩家名称和积分,因为哈希表支持高效的插入、查找和删除操作。
  2. 操作实现

    • 插入或更新积分:直接更新哈希表中的玩家积分。
    • 查询积分:查找哈希表,存在则输出积分,否则输出 Not found
    • 删除玩家:从哈希表中删除玩家,存在则输出删除成功,否则输出 Not found
    • 统计玩家总数:输出哈希表的大小。
  3. 性能优化

    • 输入输出优化:使用 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 来模拟贪吃蛇的身体。队列从头到尾存放蛇身各部分的坐标,队头为蛇尾,队尾为蛇头。

系统会调用以下两个函数,你需要实现它们的逻辑:

  1. moveSnack 函数:moveSnack 函数接受一个方向参数 dir,dir ∈ {1, 2, 3, 4},分别表示上下左右移动。函数功能如下:

    • 将蛇头向对应方向移动一个单位长度;
    • 身体的其他部分依次跟随前移一格;
    • 如果移动后蛇头与身体其他部分重叠,则函数返回 true,表示会撞到自己,此时不执行任何移动操作;否则返回 false。
  2. 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、解题思路
  1. 数据结构

    • 使用双端队列 deque 来存储蛇身的坐标,队头为蛇尾,队尾为蛇头。
  2. 移动逻辑

    • 根据方向 dir 计算蛇头的新位置。
    • 检查新位置是否与蛇身其他部分重叠。
    • 如果不重叠,移除队头(蛇尾),加入新蛇头到队尾;否则返回 true
  3. 吃果子逻辑

    • 类似移动逻辑,但不移除队头,直接在队尾添加新蛇头,并在队头添加一个反向移动的蛇尾延长部分。
    • 同样检查碰撞。
  4. 方向处理

    • 定义方向数组 dxdy 对应上下左右的移动。
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;
}


评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lenyiin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值