语法习题总结
输入输出、表达式与顺序语句
- 钞票
在这个问题中,你需要读取一个整数值并将其分解为多张钞票的和,每种面值的钞票可以使用多张,并要求所用的钞票数量尽可能少。
请你输出读取值和钞票清单。
钞票的可能面值有100,50,20,10,5,2,1。
输入格式
输入一个整数N。
输出格式
参照输出样例,输出读取数值以及每种面值的钞票的需求数量。
数据范围
0<N<1000000
#include<iostream>
using namespace std;
int main(){
int input;
cin >> input;
cout << input << endl;
int money[] = {100, 50, 20, 10, 5, 2, 1};
for(int i = 0; i < 7; i++){
printf("%d nota(s) de R$ %d,00\n", input / money[i], money[i]);
input %= money[i];
}
return 0;
}
本题有两个注意点:
1、可以用数组先存储所有的面值然后再用循环来求解,避免一个一个死求。
2、先相除再取余是一个常用操作,不要除完之后再用原数做差求余数。
- 钞票和硬币
读取一个带有两个小数位的浮点数,这代表货币价值。
在此之后,将该值分解为多种钞票与硬币的和,每种面值的钞票和硬币使用数量不限,要求使用的钞票和硬币的数量尽可能少。
钞票的面值是100,50,20,10,5,2。
硬币的面值是1,0.50,0.25,0.10,0.05和0.01。
输入格式
输入一个浮点数N。
输出格式
参照输出样例,输出每种面值的钞票和硬币的需求数量。
数据范围
0≤N≤1000000.00
#include<iostream>
using namespace std;
int main(){
double money, tmp, b;
int a, cnt;
cin >> money;
tmp = money;
int paper[] = {100, 50, 20, 10, 5, 2, -1};
double coin[] = {1, 0.50, 0.25, 0.10, 0.05, 0.01, -1};
a = (int)tmp;
b = money - a;
cout << "NOTAS:" << endl;
for(int i = 0; paper[i] > 0; i++){
printf("%d nota(s) de R$ %d.00\n", a / paper[i], paper[i]);
a %= paper[i];
}
b += a;
b += 1e-10; // ???????
cout << "MOEDAS:" << endl;
for(int i = 0; coin[i] > 0; i++){
printf("%d moeda(s) de R$ %.2f\n", (int)(b / coin[i]), coin[i]);
b -= (int)(b / coin[i]) * coin[i];
}
return 0;
}
这道题目和上一题相比增加了小数部分,思路是可以把整数和小数部分分开计算。这就涉及到几个知识点:
1、如何把一个浮点数分成整数部分和小数部分?
a = (int)tmp; b = money - a
这两句就可以做到
2、浮点数无法求模应该怎么处理?
用最原始的办法。整数部分 (int)(b / coin[i])
小数部分 b -= (int)(b / coin[i]) * coin[i]
。
3、计算完整数部分的情况之后要把余下的整数加到小数部分。这里为了避免浮点数下溢 需要再加上一个很小的数做为补偿!
因为这里b不是做的求模运算,而是连续相处,会有精度损失!
- 时间转换
读取一个整数值,它是工厂中某个事件的持续时间(以秒为单位),请你将其转换为小时:分钟:秒来表示。
输入格式
输入一个整数N。
输出格式
输出转换后的时间表示,格式为“hours:minutes:seconds”。
数据范围
1≤N≤1000000
#include<iostream>
using namespace std;
int main(){
int time, h, m, s;
cin >> time;
h = time / 3600;
m = time % 3600 / 60;
s = time % 60;
cout << h << ":" << m << ":" << s;
return 0;
}
本题考查的也是一个相除、求模运算。天数转换也是同理,如下:
#include<iostream>
using namespace std;
int main(){
int input, y, m, d;
cin >> input;
y = input / 365;
input %= 365;
m = input / 30;
d = input % 30;
cout << y << " ano(s)" << endl;
cout << m << " mes(es)" << endl;
cout << d << " dia(s)" << endl;
return 0;
}
判断语句
- 游戏时间
读取两个整数A和B,表示游戏的开始时间和结束时间,以小时为单位。
然后请你计算游戏的持续时间,已知游戏可以在一天开始并在另一天结束,最长持续时间为24小时。
如果A与B相等,则视为持续了24小时。
输入格式
共一行,包含两个整数A和B。
输出格式
输出格式为“O JOGO DUROU X HORA(S)”,其中X为游戏持续时间。
数据范围
0≤A,B≤23
#include <cstdio>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
int res;
if (a < b) res = b - a;
else res = b - a + 24;
printf("O JOGO DUROU %d HORA(S)\n", res);
return 0;
}
本题的关键在于当a>b的时候,需要给b补偿24小时再做差。
- 游戏时间2
读取四个整数A,B,C,D,用来表示游戏的开始时间和结束时间。
其中A和B为开始时刻的小时和分钟数,C和D为结束时刻的小时和分钟数。
请你计算游戏的持续时间。
比赛最短持续1分钟,最长持续24小时。
输入格式
共一行,包含四个整数A,B,C,D。
输出格式
输出格式为“O JOGO DUROU X HORA(S) E Y MINUTO(S)”,表示游戏共持续了X小时Y分钟。
数据范围
0≤A,C≤230≤A,C≤23,
0≤B,D≤59
#include<iostream>
using namespace std;
int main(){
int a, b, c, d;
cin >> a >> b >> c >> d;
int start, end;
start = a * 60 + b;
end = c * 60 + d;
if(start >= end) end += 24 * 60;
printf("O JOGO DUROU %d HORA(S) E %d MINUTO(S)", (end - start) / 60, (end - start) % 60);
return 0;
}
本题的技巧是先统一单位为分钟最后再做单位转换!
循环语句
- 简单斐波那契数列
以下数列0 1 1 2 3 5 8 13 21 …被称为斐波纳契数列。
这个数列从第3项开始,每一项都等于前两项之和。
输入一个整数N,请你输出这个序列的前N项。
输入格式
一个整数N。
输出格式
在一行中输出斐波那契数列的前N项,数字之间用空格隔开。
数据范围
0<N<46
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int a = 0, b = 1;
for (int i = 0; i < n; i ++ )
{
cout << a << ' ';
int c = a + b;
a = b;
b = c;
}
cout << endl;
return 0;
}
这是斐波那契数列最基本的一种做法。分析思路如下:
- 完全数
一个整数,除了本身以外的其他所有约数的和如果等于该数,那么我们就称这个整数为完全数。
例如,6就是一个完全数,因为它的除了本身以外的其他约数的和为 1+2+3 = 6。
现在,给定你N个整数,请你依次判断这些数是否是完全数。
输入格式
第一行包含整数N,表示共有N个测试用例。
接下来N行,每行包含一个需要你进行判断的整数X。
输出格式
每个测试用例输出一个结果,每个结果占一行。
如果测试数据是完全数,则输出“X is perfect”,其中X是测试数据。
如果测试数据不是完全数,则输出“X is not perfect”,其中X是测试数据。
数据范围
1≤N≤1001≤N≤100,
1≤X≤108
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
int s = 0;
for (int i = 1; i * i <= x; i ++ )
if (x % i == 0)
{
s += i;
if (x / i != i&& x / i < x) s += x / i;
}
if (s == x) printf("%d is perfect\n", x);
else printf("%d is not perfect\n", x);
}
return 0;
}
本题为了避免TLE,需要做一些优化,即如果i是x的约数则x/i也是x的约数,所以没有必要遍历每个数,只要找到前i个即可。则有 i < = x i <= \sqrt x i<=x 。千万注意等号不能丢
根据题意,完全数的约数不能包含自己,并且应该避免完全平方数的相同约数加两次,所以需要做一些特判。x / i != i
是为了避免完全平方数的相同约数加两次。x / i < x
是为了避免把自己作为约数。
数学知识补充: 1 0 8 10^8 108 以内,完全数只有:6, 28, 496, 8128, 33550336
- 菱形
输入一个奇数n,输出一个由‘*’构成的n阶实心菱形。
输入格式
一个奇数n。
输出格式
输出一个由‘*’构成的n阶实心菱形。
具体格式参照输出样例。
数据范围
1≤n≤99
*
***
*****
***
*
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;
cin >> n;
int cx = n / 2, cy = n / 2;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
if (abs(i - cx) + abs(j - cy) <= n / 2) cout << '*';
else cout << ' ';
cout << endl;
}
return 0;
}
数学知识:曼哈顿距离,Manhattan Distance<(x1, y1,), (x0, y0)> = |x1 - x0| + |y1 - y0|
所以对于一个边长为n(n为奇数)的正方形,我们需要的菱形部分就是曼哈顿距离小于等于 int(n/2)的部分
数组
- 平方矩阵
输入整数N,输出一个N阶的回字形二维数组。
数组的最外层为1,次外层为2,以此类推。
输入格式
输入包含多行,每行包含一个整数N。
当输入行为N=0时,表示输入结束,且该行无需作任何处理。
输出格式
对于每个输入整数N,输出一个满足要求的N阶二维数组。
每个数组占N行,每行包含N个用空格隔开的整数。
每个数组输出完毕后,输出一个空行。
数据范围
0≤N≤100
1 1 1 1 1
1 2 2 2 1
1 2 3 2 1
1 2 2 2 1
1 1 1 1 1
#include <iostream>
using namespace std;
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
int up = i, down = n - i + 1, left = j, right = n - j + 1;
cout << min(min(up, down), min(left, right)) << ' ';
}
cout << endl;
}
cout << endl;
}
return 0;
}
数学知识:
通过观察不难发现,该矩阵的每个位置的值都是该位置到四条边的距离的最小值。
- 平方矩阵2
输入整数N,输出一个N阶的二维数组。
数组的形式参照样例。
输入格式
输入包含多行,每行包含一个整数N。
当输入行为N=0时,表示输入结束,且该行无需作任何处理。
输出格式
对于每个输入整数N,输出一个满足要求的N阶二维数组。
每个数组占N行,每行包含N个用空格隔开的整数。
每个数组输出完毕后,输出一个空行。
数据范围
0≤N≤100
1 2 3 4 5
2 1 2 3 4
3 2 1 2 3
4 3 2 1 2
5 4 3 2 1
#include<iostream>
#include<cmath>
using namespace std;
int main(){
int n, m[110][110];
while(cin >> n, n){
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++) m[i][j] = abs(i - j) + 1;
}
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
cout << m[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
return 0;
}
不难发现这是一个对称矩阵。并且每个位置的值为 m[i][j] = abs(i - j) + 1
- 蛇形矩阵
输入两个整数n和m,输出一个n行m列的矩阵,将数字 1 到 n*m 按照回字蛇形填充至矩阵中。
具体矩阵形式可参考样例。
输入格式
输入共一行,包含两个整数n和m。
输出格式
输出满足要求的矩阵。
矩阵占n行,每行包含m个空格隔开的整数。
数据范围
1≤n,m≤100
1 2 3
8 9 4
7 6 5
#include<iostream>
using namespace std;
int res[110][110];
int main(){
int n, m;
cin >> n >> m;
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
for(int x = 0, y = 0, d = 0, k = 1; k <= n * m; k++){
res[x][y] = k;
int x2 = x + dx[d], y2 = y + dy[d];
if(x2 >= n || x2 < 0 || y2 >= m || y2 < 0 || res[x2][y2]){
d = (d + 1) % 4;
x2 = x + dx[d], y2 = y + dy[d];
}
x = x2, y = y2;
}
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++) cout << res[i][j] << " ";
cout << endl;
}
}
偏移量技巧!
根据题目意思,总共有4个移动方向:右、下、左、上 依次循环,为此可以定义出四个偏移量:(0, 1) (1, 0) (0, -1) (-1, 0) !
接下来分析需要改变方向的情况:1、越界;2、重复
代码实现时需要注意几个细节:
1、定义 res[110][110]
为全局变量, 则每个位置的初始值都是0,因为我们要从1开始填入数字,所以0就可以代表该位置还没有填过数字。
2、在写循环的时候,需要定义多个初始条件:
① 开始的位置是 x = 0, y = 0
② 开始的偏移量是 d = 0
③ 从1开始填入数字,所以 k = 1
3、在循环体内,我们应该首先填入一个数字,然后计算偏移量,再判断如果执行了该偏移量是否改变方向,如果需要则重新计算改变方向后的偏移量,再更新当前的位置。
在计算方向的时候又用到了求模的技巧:d = (d + 1) % 4
- 数组旋转
输入一个n,再输入n个整数。将这个数组顺时针旋转k(k <= n)次,最后将结果输出。
例如一个数组:1 2 3 4 5 6
旋转1次: 2 3 4 5 6 1
旋转2次: 3 4 5 6 1 2
旋转3次: 4 5 6 1 2 3
旋转4次: 5 6 1 2 3 4
…
以旋转3次为例,可以分成三个步骤:
第一步:把整个数组翻转:6 5 4 3 2 1
第二步:把前3个翻转:4 5 6 3 2 1
第三部: 把最后6-3个数翻转:4 5 6 1 2 3
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n, k;
int a[100];
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> a[i];
reverse(a);
reverse(a, a + k);
reverse(a + k);
for(int i = 0; i < 0; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
使用函数 reverse(),在头文件中
字符串
- 循环相克令(石头、剪刀、布)
循环相克令是一个两人玩的小游戏。
令词为“猎人、狗熊、枪”,两人同时说出令词,同时做出一个动作——猎人的动作是双手叉腰;狗熊的动作是双手搭在胸前;枪的动作是双手举起呈手枪状。
双方以此动作判定输赢,猎人赢枪、枪赢狗熊、狗熊赢猎人,动作相同则视为平局。
现在给定你一系列的动作组合,请你判断游戏结果。
输入格式
第一行包含整数T,表示共有T组测试数据。
接下来T行,每行包含两个字符串,表示一局游戏中两人做出的动作,字符串为“Hunter”, “Bear”, “Gun”中的一个,这三个单词分别代表猎人,狗熊和枪。
输出格式
如果第一个玩家赢了,则输出“Player1”。
如果第二个玩家赢了,则输出“Player2”。
如果平局,则输出“Tie”。
数据范围
1≤N≤100
#include<iostream>
#include<cstring>
using namespace std;
int main(){
int n;
cin >> n;
while(n--){
string s1, s2;
cin >> s1 >> s2;
// use numbers to map the string, easy to compare
int x, y;
if(s1 == "Hunter") x = 0;
else if(s1 == "Bear") x = 1;
else x = 2;
if(s2 == "Hunter") y = 0;
else if(s2 == "Bear") y = 1;
else y = 2;
if(x == y) puts("Tie");
else if(x == (y + 1) % 3) puts("Player1"); // modulo is often used when there's a circle!
else puts("Player2");
}
return 0;
}
本题用到两个小技巧:
1、将字符串的含义用数字表示
2、用到“循环”的地方就常常用到求模的技巧
- 信息加密
在传输信息的过程中,为了保证信息的安全,我们需要对原信息进行加密处理,形成加密信息,从而使得信息内容不会被监听者窃取。
现在给定一个字符串,对其进行加密处理。
加密的规则如下:
- 字符串中的小写字母,a加密为b,b加密为c,…,y加密为z,z加密为a。
- 字符串中的大写字母,A加密为B,B加密为C,…,Y加密为Z,Z加密为A。
- 字符串中的其他字符,不作处理。
请你输出加密后的字符串。
输入格式
共一行,包含一个字符串。注意字符串中可能包含空格。
输出格式
输出加密后的字符串。
数据范围
输入字符串的长度不超过100。
#include<iostream>
using namespace std;
int main(){
string s;
getline(cin, s);
for(char &c : s){
if(c >= 'a' && c <= 'z'){
c = (c + 1 - 'a') % 26 + 'a';
}
else if(c >= 'A' && c <= 'Z'){
c = (c + 1 - 'A') % 26 + 'A';
}
}
cout << s;
return 0;
}
本题还是考察求模的技巧,只不过不是从0开始的,总而言之方法就是: 当前位置+1 减去起始位置 模上总长度 !!
- 输出字符串
给定一个字符串a,请你按照下面的要求输出字符串b。
给定字符串a的第一个字符的ASCII值加第二个字符的ASCII值,得到b的第一个字符;
给定字符串a的第二个字符的ASCII值加第三个字符的ASCII值,得到b的第二个字符;
…
给定字符串a的倒数第二个字符的ASCII值加最后一个字符的ASCII值,得到b的倒数第二个字符;
给定字符串a的最后一个字符的ASCII值加第一个字符的ASCII值,得到b的最后一个字符。
输入格式
输入共一行,包含字符串a。注意字符串中可能包含空格。
数据保证字符串内的字符的ASCII值均不超过63。
输出格式
输出共一行,包含字符串b。
数据范围
2≤a的长度≤100
#include<iostream>
using namespace std;
int main(){
string s, r;
getline(cin, s);
for(int i = 0; i < s.size(); i++){
r += s[i] + s[(i + 1) % s.size()];
}
cout << r;
return 0;
}
本题考查的还是求模的技巧!同上
- 字符串插入
有两个不包含空白字符的字符串str和substr,str的字符个数不超过10,substr的字符个数为3。(字符个数不包括字符串结尾处的’\0’。)
将substr插入到str中ASCII码最大的那个字符后面,若有多个最大则只考虑第一个。
输入格式
输入包括若干行,每一行为一组测试数据,格式为
str substr
输出格式
对于每一组测试数据,输出插入之后的字符串。
#include<iostream>
using namespace std;
int main(){
string a, b;
// pay attention to the stop condition! no inputs, stop
while(cin >> a >> b){
int index = 0; // index must be refreshed after each loop
for(int i = 0 ; a[i]; i++){
if(a[i] > a[index]){
index = i;
}
}
cout << a.substr(0, index + 1) + b + a.substr(index + 1) << endl;
}
return 0;
}
使用了函数 substr()
. 左闭右开!
- 只出现一次的字符
给你一个只包含小写字母的字符串。
请你判断是否存在只在字符串中出现过一次的字符。
如果存在,则输出满足条件的字符中位置最靠前的那个。
如果没有,输出”no”。
输入格式
共一行,包含一个由小写字母构成的字符串。
数据保证字符串的长度不超过100000。
输出格式
输出满足条件的第一个字符。
如果没有,则输出”no”。
#include<iostream>
using namespace std;
int main(){
string s;
int n[26] = {0};
cin >> s;
for(char &c : s) n[c - 'a']++;
for(char &c :s){
if(n[c - 'a'] == 1){
cout << c;
return 0;
}
}
cout << "no";
return 0;
}
本题只需注意一个小技巧:对于这种输出满足条件的第一答案的题目
当循环体找到了答案可以直接return,而不要用break,否则在还需要判断是否整个循环已经结束。
- 忽略大小写比较字符串大小
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
string a, b;
getline(cin, a);
getline(cin, b);
transform(a.begin(), a.end(), a.begin(), ::tolower);
transform(b.begin(), b.end(), b.begin(), ::tolower);
if(a < b) cout << "<";
else if(a == b) cout << "=";
else cout << ">";
return 0;
}
使用函数 transform()
.
- 去掉多余的空格
输入一个字符串,字符串中可能包含多个连续的空格,请将多余的空格去掉,只留下一个空格。
输入格式
共一行,包含一个字符串。
输出格式
输出去掉多余空格后的字符串,占一行。
数据范围
输入字符串的长度不超过200。
#include<iostream>
using namespace std;
int main(){
string s;
// pay attention to "while(cin >> s)", in this way we can read string until encounter '\n'
while(cin >> s) cout << s << " ";
return 0;
}
cin不读入空格
法二:双指针算法
#include<iostream>
using namespace std;
int main(){
string s, r;
getline(cin , s);
for(int i = 0; i < s.size(); i++){
if(s[i] != ' ') r += s[i];
else{
r += ' ';
// double pointer algorithm
int j = i;
while(j < s.size() && s[j] == ' ') j++;
i = j - 1;
}
}
cout << r;
return 0;
}
首先一个指针 i 用来扫描当前位置,如果不是空格就把字符复制到res中,如果是空格,那就先写入一个空格,并在此时再开一个指针 j,从当前位置开始往后移,如果是空格就已知向后移动直到不是空格为止。此时 j 找到了接下来要输入的字符,则把 i 移到当前位置。
- 字符串中最长的连续出现的字符
求一个字符串中最长的连续出现的字符,输出该字符及其出现次数,字符串中无空白字符(空格、回车和tab),如果这样的字符不止一个,则输出第一个。
输入格式
第一行输入整数N,表示测试数据的组数。
每组数据占一行,包含一个不含空白字符的字符串,字符串长度不超过200。
输出格式
共一行,输出最长的连续出现的字符及其出现次数,中间用空格隔开。
#include<iostream>
using namespace std;
int main(){
int n;
cin >> n;
string s;
while(n--){
cin >> s;
int max = 0;
char tmp;
for(int i = 0; i < s.size(); i++){
int j = i;
while(j < s.size() && s[i] == s[j]) j++;
if(j - i > max){
max = j - i;
tmp = s[i];
}
i = j - 1;
}
cout << tmp << " " << max << endl;
}
return 0;
}
本题也是双指针算法 !
- 单词替换
输入一个字符串,以回车结束(字符串长度不超过100)。
该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写。
现需要将其中的某个单词替换成另一个单词,并输出替换之后的字符串。
输入格式
输入共3行。
第1行是包含多个单词的字符串 s;
第2行是待替换的单词a(长度不超过100);
第3行是a将被替换的单词b(长度不超过100)。
输出格式
共一行,输出将s中所有单词a替换成b之后的字符串。
#include<iostream>
#include<sstream>
using namespace std;
int main(){
string a, b, c;
getline(cin, a);
cin >> b >> c;
stringstream ssin(a);
string tmp;
while(ssin >> tmp){
if(tmp == b) cout << c << " ";
else cout << tmp << " ";
}
return 0;
}
使用stringstream 在头文件中 “Characters can be inserted and/or extracted from the stream using any operation allowed on both input and output streams.”
ssin(a) 只是一种命名习惯,这样定义后,ssin和cin完全等价。只不过不是从控制台输入而是把字符串“a”当作了输入流。
- 最长的单词
一个以’.’结尾的简单英文句子,单词之间用空格分隔,没有缩写形式和其它特殊形式,求句子中的最长单词。
输入格式
输入这个简单英文句子,长度不超过500。
输出格式
该句子中最长的单词。如果多于一个,则输出第一个。
#include<iostream>
using namespace std;
int main(){
string s, r;
while(cin >> s){
if(s.back() == '.') s.pop_back();
if(s.size() > r.size()) r = s;
}
cout << r;
return 0;
}
使用函数 .back() 返回字符串的最后一个字符;.pop_back()删除字符串的最后一个字符
此外,根据此题我们也可以看到,没有初始化的字符串的长度是0
- 倒排单词
编写程序,读入一行英文(只包含字母和空格,单词间以单个空格分隔),将所有单词的顺序倒排并输出,依然以单个空格分隔。
输入格式
输入为一个字符串(字符串长度至多为100)。
输出格式
输出为按要求排序后的字符串。
#include<iostream>
using namespace std;
int main(){
string s[110];
int n = 0;
while(cin >> s[n]) n++;
for(int i = n - 1; i >= 0; i--) cout << s[i] << " ";
return 0;
}
本题使用的是字符串数组!它可以在数组的每个位置存一个字符串。
- 字符串移位包含问题
对于一个字符串来说,定义一次循环移位操作为:将字符串的第一个字符移动到末尾形成新的字符串。
给定两个字符串s1和s2,要求判定其中一个字符串是否是另一字符串通过若干次循环移位后的新字符串的子串。
例如CDAA是由AABCD两次移位后产生的新串BCDAA的子串,而ABCD与ACBD则不能通过多次移位来得到其中一个字符串是新串的子串。
输入格式
共一行,包含两个字符串,中间由单个空格隔开。
字符串只包含字母和数字,长度不超过30。
输出格式
如果一个字符串是另一字符串通过若干次循环移位产生的新串的子串,则输出true,否则输出false。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string a, b, res, tmp;
cin >> a >> b;
if(a.size() < b.size()) swap(a, b);
int len = a.size();
while(len--){
res = a.substr(1, a.size()) + a[0];
a = res;
if(res.find(b) != string::npos){
cout << "true";
return 0;
}
}
cout << "false";
return 0;
}
使用函数swap(a, b) 交换a和b的内容。内容的类型不限;使用.find() 函数查找字符串中是否包含指定内容,该函数在头文件中
- 字符串乘方
给定两个字符串a和b,我们定义a*b为他们的连接。
例如,如果a=”abc” 而b=”def”, 则a*b=”abcdef”。
如果我们将连接考虑成乘法,一个非负整数的乘方将用一种通常的方式定义:a0a0=””(空字符串),a(n+1)a(n+1)=a∗(an)a∗(an)。
输入格式
输入包含多组测试样例,每组测试样例占一行。
每组样例包含一个字符串s,s的长度不超过100。
最后的测试样例后面将是一个点号作为一行。
输出格式
对于每一个s,你需要输出最大的n,使得存在一个字符串a,让 s = a n s = a^n s=an
#include<iostream>
using namespace std;
int main(){
string s;
while(cin >> s, s != "."){
for(int i = 1; i <= s.size(); i++){
string tmp, res;
if(s.size() % i != 0) continue;
tmp = s.substr(0, i);
for(int j = 0; j < s.size() / i; j++) res += tmp;
if(res == s){
cout << s.size() / i << endl;
break;
}
}
}
return 0;
}
本题关键是s的长度一定要能够整出a的长度。
- 字符串最大跨距
#include<iostream>
#include<cstring>
using namespace std;
int main(){
string s, s1, s2;
char c;
// while(cin >> c && c != ',') s += c;
// while(cin >> c && c != ',') s1 += c;
// while(cin >> c) s2 += c;
getline(cin, s, ',');
getline(cin, s1, ',');
getline(cin, s2);
if(s.size() < s1.size() || s.size() < s2.size()) puts("-1");
else{
int l = 0;
while(l + s1.size() < s.size()){
int i = 0;
while(i < s1.size()){
if(s[l + i] != s1[i]) break;
i++;
}
if(i == s1.size()) break;
l++;
}
int r = s.size() - s2.size();
while(r >= 0){
int j = 0;
while(j < s2.size()){
if(s[r + j] != s2[j]) break;
j++;
}
if(j == s2.size()) break;
r--;
}
l += s1.size() - 1;
if(l >= r) puts("-1");
else cout << r - l - 1;
}
return 0;
}
1、函数getline(cin, s, ‘,’) 可以以逗号作为分隔符输入。
2、注意 l += s1.size() - 1
这句话的运算顺序! +=
是赋值的符号,它的优先级是低于数学运算的。所以是先计算了 s1.size() - 1
。
3、本题采用了两次双指针算法 ,指针 l
从左向右扫描,每次扫描的同时,创建指针 i
从 l
所在的位置开始向后扫描 s1.size()
个长度,一旦发现了不匹配的就结束扫描。这时再根据 i
的位置判断它是否已经扫描完了 s1.size()
个长度,如果是则结束指针 l
的扫描。指针 r
从右向左扫描
4、最后需要判断 l
和 r
是否有重合或者交叉。
法二:
#include<iostream>
using namespace std;
int main(){
string s, s1, s2;
getline(cin, s, ',');
getline(cin, s1, ',');
getline(cin, s2);
if(s.size() < s1.size() || s.size() < s2.size()) puts("-1");
else{
int l = 0;
while(s.substr(l, s1.size()) != s1 && l + s1.size() <= s.size()) l++;
int r = s.size() - s2.size();
while(s.substr(r, s2.size()) != s2 && r) r--;
l += s1.size() - 1;
if(l >= r) puts("-1");
else cout << r - l - 1;
}
return 0;
}
本题可以多利用 substr( ) 函数来避免使用两次刷个指针,可以大大减小代码量。
- 最长公共字符串后缀
给出若干个字符串,输出这些字符串的最长公共后缀。
输入格式
由若干组输入组成。
每组输入的第一行是一个整数N。
N为0时表示输入结束,否则后面会继续有N行输入,每行是一个字符串(字符串内不含空白符)。
每个字符串的长度不超过200。
输出格式
共一行,为N个字符串的最长公共后缀(可能为空)。
数据范围
1≤N≤200
#include<iostream>
using namespace std;
const int N = 200;
string s[N];
int main(){
int n;
while(cin >> n, n){
int len = 1000;
for(int i = 0; i < n; i++){
cin >> s[i];
if(len > s[i].size()) len = s[i].size();
}
while(len){
bool success = true;
for(int i = 1; i < n; i++){
bool is_same = true;
for(int j = 1; j <= len; j++){
if(s[0][s[0].size() - j] != s[i][s[i].size() - j]){
is_same = false;
break;
}
}
if(!is_same){
success = false;
break;
}
}
if(success) break;
len--;
}
cout << s[0].substr(s[0].size() - len) << endl;
}
return 0;
}
1、首先求出所有输入的字符串中的最小长度。因为公共的字符肯定最短的字符串也必须包含。并且先假设最短的字符串就是公共后缀。
2、既然要判断是否有公共的字符,就让每个字符串和第一个字符串作比较就行。并且是从最后一位开始比较,总共需要比较的位数就是最短字符串的长度!如果发现最短字符串不是公共后缀,说明后缀的长度还需要减小。
法二:
#include<iostream>
using namespace std;
const int N = 200;
string s[N];
int main(){
int n;
while(cin >> n, n){
int len = 1000;
for(int i = 0; i < n; i++){
cin >> s[i];
if(len > s[i].size()) len = s[i].size();
}
for(int i = 1; i < n; i++){
while(s[i].substr(s[i].size() - len) != s[0].substr(s[0].size() - len)) len--;
}
cout << s[0].substr(s[0].size() - len) << endl;
}
return 0;
}
因为本题的核心就是检查每个字符串的最后”len“个字符是否和第一个字符串相同,所以如果不是为了锻炼自己写循环的能力的话,可以都用substr( ) 函数来写!!
特别需要注意的是该函数的用法 substr(start, len) , 如果不给第二个参数则默认一直取到字符串末尾。
函数
- 数组去重
#include<iostream>
using namespace std;
int unique(int n[], int size){
int cnt = 0;
for(int i = 0; i < size; i++){
bool exist = false;
for(int j = 0; j < i; j++){
if(n[i] == n[j]){
exist = true;
break;
}
}
if(!exist) cnt++;
}
return cnt;
}
int main(){
int n, size;
int m[1010];
cin >> n >> size;
for(int i = 0; i < n; i++) cin >> m[i];
cout << n - size + unique(m, size);
return 0;
}
本题技巧:
1、灵活运用bool类型变量
2、第二层循环判断 n[i] 以前的元素,而不要向后遍历。这样即使是初始情况 i=0,也能很好的应对。
- 跳台阶
一个楼梯共有n级台阶,每次可以走一级或者两级,问从第0级台阶走到第n级台阶一共有多少种方案。
输入格式
共一行,包含一个整数n。
输出格式
共一行,包含一个整数,表示方案数。
数据范围
1≤n≤15
法一:数学公式法
#include<iostream>
using namespace std;
long factorial(int n){
if(n <= 1) return 1;
else return n * factorial(n - 1);
}
int combinatorial(int n, int m){
return factorial(m) / (factorial(n) * factorial(m - n));
}
int main(){
int n, res = 0;
cin >> n;
for(int i = 0; i <= n / 2; i++) res += combinatorial(i, n - i);
cout << res;
return 0;
}
例如跳5个台阶: r e s = C 5 0 + C 4 1 + C 3 2 res = C_5^0 + C_4^1 + C_3^2 res=C50+C41+C32 。首先5个台阶之多能有 ⌊ 5 / 2 ⌋ = 2 \lfloor 5 / 2 \rfloor = 2 ⌊5/2⌋=2 次跳两级。所以根据跳两级台阶的次数能分出三种情况:0次跳两级,1次跳两级,2次跳两级。
法二:斐波那契数列法:
#include<iostream>
using namespace std;
int fib(int n){
if(n <= 0) return 0;
else if(n == 1) return 1;
else return fib(n - 1) + fib(n - 2);
}
int main(){
int n;
cin >> n;
cout << fib(n + 1);
return 0;
}
不难发现,到第n级台阶的总步数 f(n) 有两个部分组成,到它前一级台阶的总步数 f(n-1) 和到它前两级台阶的总步数 f(n - 2) 所以有:f(n) = f(n - 1) + f(n - 2)
法三:深度优先(递归)搜索树 DFS
#include<iostream>
using namespace std;
int n;
int ans = 0;
// recursive search tree (depth first search)
/*
thinking steps:
1. draw the dfs tree
2. determin the variables that need to store
3. determin the boundary condition
*/
void dfs(int k){
if(k == n) ans++;
else if(k < n){
dfs(k + 1);
dfs(k + 2);
}
}
int main(){
cin >> n;
dfs(0);
cout << ans;
return 0;
}
我们需要记录的是当前所在的级数即代码中的 k
每当k=n的时候就是搜索出了一条路径。
- 走方格
给定一个n*m的方格阵,沿着方格的边线走,从左上角(0,0)开始,每次只能往右或者往下走一个单位距离,问走到右下角(n,m)一共有多少种不同的走法。
输入格式
共一行,包含两个整数n和m。
输出格式
共一行,包含一个整数,表示走法数量。
数据范围
1≤n,m≤10
#include<iostream>
// use dfs.
using namespace std;
int ans = 0;
int n, m;
void dfs(int x, int y){
if(x == n && y == m) ans++;
else{
if(x < n) dfs(x + 1, y);
if(y < m) dfs(x, y + 1);
}
}
int main(){
cin >> n >> m;
dfs(0, 0);
cout << ans;
return 0;
}
本题同样是用的深度优先递归搜索树。需要记录的是当前的坐标,每当横纵坐标都达到边界的时候就是搜索到了一个答案。
- 排列
给定一个整数n,将数字1~n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤9
#include<iostream>
using namespace std;
const int N = 10;
int n;
void dfs(int k, int nums[], bool state[]){
if(k > n){
for(int i = 1; i <= n; i++) cout << nums[i] << " ";
cout << endl;
}
else{
for(int i = 1; i <= n; i++){
if(!state[i]){
state[i] = true;
nums[k] = i;
dfs(k + 1, nums, state);
state[i] = false; // restore the scene!
}
}
}
}
int main(){
cin >> n;
int nums[N];
bool state[N] = {0};
dfs(1, nums, state);
return 0;
}
同样使用深度优先递归搜索树。本题需要存储的信息有:
1、需要用来填的数字
2、每个数字是否已经被用过,这里特别要当心在搜索完一个路径之后进行下一次搜索之前对于数字的使用情况要回复现场!!!
3、当前正在填第几个位置。需要注意本题是每当填完了n个位置之后才算搜索到了一条路径,因为代码中是从位置1开始搜索的,所以停止条件是k>n
类、结构体、指针、引用
- 在O(1)时间删除链表结点
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
本题的技巧在于只给了当前需要删除的节点的指针而不知道它的前一个节点。所以可以让当前节点先变成下一个节点然后再指向下下个节点即可
- 合并两个排序的链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(-1);
// ListNode *cur = dummy; // current pointer start from the head
// auto dummy = new ListNode(-1);
// at this time cur and dummy are two pointers point to the same linkedliest!
// but dummy is stationary cur will move to always point to the rear node.
auto cur = dummy;
// auto &cur = dummy;
// check each linkedlist until one reaches the rear node
while(l1 && l2){
if(l1->val < l2->val){
cur->next = l1; // merge it to the linkedlist whose head node is dummy!
cur = l1; // refresh cur
l1 = l1->next; // refresh l1
}
else{
cur->next = l2;
cur = l2;
l2 = l2->next;
}
}
// find the remained nodes add them at last
if(l1) cur->next = l1;
else cur->next = l2;
return dummy->next;
}
};
- 把字符串转换成整数
不能使用atoi或者其他类似的库函数。
你的函数应满足下列条件:
- 忽略所有行首空格,找到第一个非空格字符,可以是 ‘+/−’ 表示是正数或者负数,紧随其后找到最长的一串连续数字,将其解析成一个整数;
- 整数后可能有任意非数字字符,请将其忽略;
- 如果整数长度为0,则返回0;
- 如果整数大于INT_MAX(2^31 − 1),请返回INT_MAX;如果整数小于INT_MIN(−2^31) ,请返回INT_MIN;
class Solution {
public:
int strToInt(string str) {
int k = 0;
long long num = 0;
bool is_negative = false;
while(k < str.size() && str[k] == ' ') k++;
if(str[k] == '+') k++;
else if(str[k] == '-'){
is_negative = true;
k++;
}
while(k < str.size() && str[k] >= '0' && str[k] <= '9'){
num = num * 10 + str[k] - '0';
k++;
}
if(is_negative) num *= -1;
if(num < INT_MIN) return INT_MIN;
else if(num > INT_MAX) return INT_MAX;
else return num;
}
};
1、去除前面的空格
2、辨识正负号
3、转换成数字 num = num * 10 + str[k] - '0'
- 链表反转
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next) return head;
auto p = head, q = head->next; // important! record two node, otherwise can't find the node before.
while(q){
auto o = q->next;
q->next = p;
p = q;
q = o; // at last q will become NULL
}
head->next = NULL;
return p;
}
};
1、空链表和单个元素的链表无需反转
2、使用两个指针p和q,每次让q的next指向p,然后移动p和q,为了记住q的下一个节点需要再用一个变量o来保存。
3、退出循环之后要记得让最初的头节点也就是翻转后的尾结点指向NULL
4、返回的是p而不能是q->next 因为最后q是NULL退出循环了。
- 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共结点。
当不存在公共节点时,返回空节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB){
auto p = headA, q = headB;
while(p != q){
if(p) p = p->next;
else p = headB;
if(q) q = q->next;
else q = headA;
}
return p;
}
};
本题技巧,利用两个指针p和q同时扫描两个链表,扫描到NULL后再分别从另一条链表的头节点开始扫描, 不难证明当连个指针相遇的时候就是第一个公共节点,因为走过的路程一样。如果两个链表没有公共节点则两个指针都会停在表尾的空节点。
- 删除链表中重复的节点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto pre = new ListNode(-1);
pre->next = head;
auto p = pre;
while(p->next){
auto q = p->next;
while(q->next && p->next->val == q->next->val) q = q->next;
if(q == p->next) p = q;
else p->next = q->next;
}
return pre->next;
}
};
本题也是用的双指针算法。定义指针p和q,让q一直向后扫描,此题的关键在于每次都是判断p的下一个节点的值是否和q的下一个节点的值相等,而不是判断p和q的值是否相等!!! 也就是说本质上p是用来记住当前节点的,q->val 和 q->next->val 才是需要比较的对象!!!
STL容器、位运算与常用库函数
- 0到n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。
在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
unordered_set<int> s;
for(int i = 0; i <= nums.size(); i++) s.insert(i);
for(auto x : nums) s.erase(x);
return *s.begin();
}
};
本题使用了哈希表,注意它的相关操作 .insert() .erase()
- 和为S的两个数字
输入一个数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。
如果有多对数字的和等于s,输出任意一对即可。
你可以认为每组输入中都至少含有一组满足条件的输出。
class Solution {
public:
vector<int> findNumbersWithSum(vector<int>& nums, int target) {
unordered_set<int> s;
for(auto x : nums){
if(s.count(target - x)) return {x, target - x};
else s.insert(x);
}
}
};
本题也是使用的哈希表,思路是在哈希表中检查target - num[i]是否在其中,如果是则返回,否则就将其加入到标中。
- 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序。
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
class Solution {
public:
void reOrderArray(vector<int> &array) {
auto p = 0;
auto q = array.size() - 1;
if(!array.empty()){
while(p < q){
if(p < array.size() && array[p] % 2) p++;
if(q < array.size() && array[q] % 2 == 0) q--;
else swap(array[p], array[q]);
}
}
}
};
本题还是用的双指针算法。p从前向后扫描,q从后向前扫描。保证p左边的都是奇数,q右边的都是偶数,如果不满足该情况就交换连个数的位置。
- 数字排列、
输入一组数字(可能包含重复数字),输出其所有的排列方式。
class Solution {
public:
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
do{
res.push_back(nums);
}while(next_permutation(nums.begin(), nums.end()));
return res;
}
};
使用函数 next_permutation() 该函数可以按照字典序把数组的顺序变成下一个字典序顺序。
- 二进制中1的个数
输入一个32位整数,输出该数二进制表示中1的个数。
class Solution {
public:
int NumberOf1(int n) {
int cnt = 0;
for(int i =0; i < 32; i++){
if(n >> i & 1 == 1) cnt++;
}
return cnt;
}
};
求x的第k位数字 x >> k & 1
- 三元组排序
给定N个三元组(x, y, z),其中x是整数,y是浮点数,z是字符串。
请你按照x从小到大的顺序将这些三元组打印出来。
数据保证不同三元组的x值互不相同。
输入格式
第一行包含整数N。
接下来N行,每行包含一个整数x,一个浮点数y,一个字符串z,表示一个三元组,三者之间用空格隔开。
输出格式
共N行,按照x从小到大的顺序,每行输出一个三元组。
注意,所有输入和输出的浮点数y均保留两位小数。
数据范围
1≤N≤100001≤N≤10000,
1≤x,y≤1051≤x,y≤105,
字符串总长度不超过100000.
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10010; // N must be constant, otherwise a[N] will throw an error!
struct data{
int x;
double y;
string s;
bool operator< (const data &t) const{
return x < t.x;
}
}a[N];
int main(){
int n;
cin >> n;
for(int i = 0; i < n; i++) cin >> a[i].x >> a[i].y >> a[i].s;
sort(a, a + n);
// when use print string in struct should add ".c_str()" !!!
for(int i = 0; i < n; i++) printf("%d %.2f %s\n", a[i].x, a[i].y, a[i].s.c_str());
return 0;
}
本题需要注意:
1、在结构体中重载小于号!
2、结构体中字符串的输出 .c_str()
3、N必须是定值否则编译不通过