目录
这是厦大2023小学期校外实训(1)的第一节课的内容,首先在这里放一些上学期C++和该课程的衔接内容,以免对一些内容仅有了解程度而无具体运用能力。
C++与校外实训衔接内容https://blog.csdn.net/weixin_75101793/article/details/131523081?spm=1001.2014.3001.5501
1.2 排序考试(巧用sort和vector)
题目再现
描述
处于某种原因,一点都不懂编程的某某系小迅老师竟然成功跳槽到计算机系来教大一新生如何编程。
看着这位在台上胡言乱语,水平比同学小华弱太多的老师,小鲁简直不忍直视。
那学期的期中考试,小讯老师出的题目竟然是:“请写一个排序算法给数组排序,结果按照升序输出。”
经过NQ49斩的小鲁分分钟就完成了代码。小讯老师一看,哎呀不得了,得提高期中考试难度。
他立刻把题目改为:“给定任意T组整数,每组整数都要按升序输出。”
小鲁笑了笑,原来这类题已经难不倒他了,原来他早就超过了大一上小讯老师的水平了!
小鲁水平进阶了,你做得到吗?
输入
第一行是整数T,表示一共有T组数据。
接下来T行,每行有N+1个数,第一个整数表示该行有N个待排序的数字。
整数N(1<=N<=1000000),T(1<=T<=100)。
输出
对于每组整数,按照升序输出排序结果,每个结果占一行。
输入样例 1
3 4 412 120 5560 3760 5 576 66 35 99 88 4 127 100 510 380输出样例 1
120 412 3760 5560 35 66 88 99 576 100 127 380 510
初始思路
利用数组和冒泡排序实现多组数据的升序输出。
#include <iostream>
using namespace std;
int main()
{
//创建数组
int T, arr[105] = {0};
cin >> T;
while (T--)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
//冒泡排序
for(int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
if (arr[i] > arr[j])
{
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//依次输出,注意最后一个数据后没有空格
for (int i = 0; i < n - 1; i++)
cout << arr[i] << " ";
cout << arr[n - 1] << endl;
}
return 0;
}
优化代码
利用vector容器代替数组提高效率,用sort库函数免去冒泡排序的具体书写。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
int T;
cin >> T;
while (T--)
{
int n, a, i;
cin >> n;
//读入每组数据
vector<int> nums;
for (i = 0; i < n; i++)
{
cin >> a;
nums.push_back(a);
}
//排序
sort(nums.begin(), nums.end());
//循环输出数组
for (i = 0; i < n; i++)
{
cout << nums[i] << " ";
}
cout << nums[i] << endl;
}
return 0;
}
以上代码在编译中没有问题,但是在运行过程中出现了问题(如图):
发现是vector越界了,在输出部分最后一个输出的应该是num[n-1],改之后运行正常。
for (i = 0; i < n-1 ; i++)
cout << nums[i] << " ";
cout << nums[i] << endl;
1.4 人的周期(跳过不必要测试)
题目再现
描述
据说人生来就有三个生理周期,分别为体力周期、感情周期和智力周期,它们的周期长度分别为23天、28天和33天。
每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,在智力周期的高峰,人会思维敏捷,注意力容易高度集中。
因为三个周期的长度不同,所以通常三个周期的高峰不会落在同一天。
对于每个人,想知道何时三个高峰落在同一天。
对于每个周期,会给出从当前年份的第一天开始,到出现高峰的天数(不一定是第一次高峰出现的时间)。
给定一个从当年第一天开始的天数,你的任务是输出从给定时间开始(不包括给定时间),下一次三个高峰落在同一天的时间(距给定时间的天数)。例如:给定时间为10,下次出现三个高峰同一天的时间是12,则输出2(注意这里不是3)。
输入
输入包含多组数据,每一组数据由四个整数组成,数据以-1 -1 -1 -1 结束。
对于每一行的四个整数p, e, i和d, 其中p, e, i分别表示体力、情感和智力高峰出现的时间(时间从当年的第一天开始计算)。
d是给定的时间,可能小于p, e或i。所有给定时间是非负的并且小于或等于365,所求的时间小于或等于21252。
输出
从给定时间起,下一次三个高峰同一天的时间(距离给定时间的天数)。
输入样例 1
0 0 0 0 0 0 0 100 5 20 34 325 4 5 6 7 283 102 23 320 203 301 203 40 -1 -1 -1 -1输出样例 1
Case 1: the next triple peak occurs in 21252 days. Case 2: the next triple peak occurs in 21152 days. Case 3: the next triple peak occurs in 19575 days. Case 4: the next triple peak occurs in 16994 days. Case 5: the next triple peak occurs in 8910 days. Case 6: the next triple peak occurs in 10789 days.
初始思路
暴力枚举,从k=d+1天开始,一直试到第21252天,看每一个k是否满足以下条件:
(k-p)%23==0 && (k-e)%28==0 && (k-i)%33==0
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int p, e, i, d, caseNumber = 0;
while(cin >> p >> e >> i >> d && p != -1)
{
++ caseNumber;
int k;
//枚举
for (k = d + 1; k <= d + 21252; k++)
{
if ((k - p) % 23 == 0 && (k - e) % 28 == 0 && (k - i) % 33 == 0)
cout << "Case " << caseNumber << ": the next triple peak occurs in " << k - d << " days." << endl;
}
}
return 0;
}
优化代码
利用三高峰日符合的条件设计搜索的步长,用空循环跳过不必要的测试日。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int p, e, i, d, caseNumber = 0;
while (cin >> p >> e >> i >> d && p != -1)
{
++caseNumber;
int k;
//用空循环找到指定的k
for (k = d + 1; (k - p) % 23; k++);
for (; (k - e) % 28; k += 23);
for (; (k - i) % 33; k += 23 * 28);
cout << "Case " << caseNumber << ": the next triple peak occurs in " << k - d << " days." << endl;
}
return 0;
}
1.5 假币问题(字符串处理)
题目再现
描述
林克有12枚银币。其中有11枚真币和1枚假币。假币看起来和真币没有区别,但是重量不同。但林克不知道假币比真币轻还是重。
于是他向他朋友约珥借了一架天平,用这架天平称了这些币三次。
如果用天平称两枚硬币,发现天平平衡,说明两枚都是真的。如果用一枚真币与另一枚银币比较,发现它比真币轻或重,说明它是假币。
经过精心的设计,聪明的林克根据这三次称量结果找出假币,并且能够确定假币是轻是重。
如果给你林克的称量数据,你也可以找出假币并且确定假币是轻是重吗?(林克提供的称量数据保证一定能找出假币)。
输入
第一行有一个数字n,表示有n组测试用例。
对于每组测试用例:
输入有三行,每行表示一次称量的结果。林克事先将银币标号为A-L。
每次称量的结果用三个以空格隔开的字符串表示:
天平左边放置的硬币 天平右边放置的硬币 平衡状态。
其中平衡状态用``up'', ``down'', 或 ``even''表示, 分别为右端高、右端低和平衡。天平左右的硬币数总是相等的。
输出
输出哪一个标号的银币是假币,并说明它比真币轻还是重(heavy or light)。
输入样例 1
1 ABCD EFGH even ABCI EFJK up ABIJ EFGH even输出样例 1
K is the counterfeit coin and it is light.
初始思路
用两个字符数组存储天平左边和右边的银币代号,并用另一个字符数组记录这一次称量的结果。但同时每一组测试用例需要三次测量,则需要三个二维数组。对于三种不同的结果可以用 switch来决定下一步的操作。
当然,真正执行起来还是要一枚一枚来判断:如果这一组为even,则本组所有成员全部为真币,则全部标记为真;如果不平,不管是哪边重,先都排除了even中的真币,在剩下的硬币中再判断是重是轻。
代码实现
#include <iostream>
#include <cstring>
using namespace std;
string Left[3];
string Right[3];
string result[3];
bool isFeitCoin(char iCoin, bool isLight) //传入硬币和该枚硬币的轻重
{
string c;
c.push_back(iCoin);
for (int i = 0; i < 3; i++)
{
string l = Left[i], r = Right[i];
if (!isLight)
swap(l, r); //如果该枚硬币较重则左右交换,使得重的永远在右侧,轻的永远在左侧
switch (result[i][0])
{
case 'e':
if (l.find(c) != string::npos || r.find(c) != string::npos)
return false;
break;
case 'u':
if (r.find(c) == string::npos)
return false;
break;
case 'd':
if (l.find(c) == string::npos)
return false;
break;
}
}
return true;
}
int main()
{
int t;
cin >> t;
while (t--)
{
for (int i = 0; i < 3; i++)
{
cin >> Left[i] >> Right[i] >> result[i];
}
for (char iCoin = 'A'; iCoin <= 'L'; iCoin++) //遍历每一枚硬币
{
if (isFeitCoin(iCoin, true))
{
cout << iCoin << " is the counterfeit coin and it is light. " << endl;
break;
}
else if (isFeitCoin(iCoin, false))
{
cout << iCoin << " is the counterfeit coin and it is heavy. " << endl;
break;
}
}
}
}
1.7 三数之和(双指针)
题目再现
描述
看着小鲁AC了第一题,小华接着提出第二问:
给定一个目标值 target,请在整数数组 a中,找出三个元素(x,y,z) 使x+y+z==target。
请找到所有满足条件的三元组,并且请按从小到大的顺序输出所有合法的三元组。
注意:三元组中不允许包含重复数字,且输出的三元组中要求 x<y<z.
例如:给定target = 17,n=7, 数组a= [0, 2, 7, 10, 15,18,25]
结果返回两个三元组:(0,2, 15), (2,7,10)
输入
输入数据为2行,第一行有两个整数 target和n,其中target代表要搜索的目标和,n表示数组a的元素个数
第二行表示数组a的n个数,每个元素用空格隔开。
输出
输出所有满足和为target的三元组(x,y,z),要求(x< y <z) 并且不允许有重复数字。
把三元组按照x的大小升序输出,x相同的按照y的大小升序输出。
输入样例 1
17 7 0 2 7 10 15 18 25输出样例 1
0 2 15 0 7 10
初始思路
利用三重循环,从最内层代表的最后一个元z开始增加,直到找到所有满足和为target的三元组(x,y,z),然后把所有可能输出。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int target, n;
cin >> target >> n;
vector<int> num(n);
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
int a,b,c;
for (a = 0; a < n-2; a++)
{
for (b = a+1; b < n - 1; b++)
{
for (c = b+1; c < n; c++)
{
if (num[a] + num[b] + num[c] == target)
cout << num[a] << " " << num[b] << " " << num[c] << endl;
}
}
}
return 0;
}
代码优化
按照上面的暴力枚举,不难发现oj只能部分通过,而未通过的部分都是运行超时:
因此我们需要优化算法,我们这里采用双指针算法。也就是先将输入的数据排序,去掉重复的数字(直接调用unique算法配合erase去重),再进行双指针循环。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<vector<int>> threeSUM(vector<int>& num, int target)
{
vector<vector<int>> res;
//双指针做法
//①排序
sort(num.begin(), num.end());
//②去重
num.erase(unique(num.begin(), num.end()), num.end());
//双指针,三重循环i,j,k,要求i<j<k
for (int i = 0; i < num.size(); i++)
{
//双指针循环
for (int j = i + 1, k = num.size() - 1; j < k; j++)
{
//预判下一个数,如果k与j没有重叠,并且对应三数之和不比target小,则移动指针k指针k--
while (j < k - 1 && num[i] + num[j] + num[k - 1] >= target)
k--;
//判断是否找到和为target的三个数
if (num[i] + num[j] + num[k] == target)
res.push_back({ num[i],num[j],num[k] });
}
}
return res;
}
bool comp(vector<int>& a, vector<int>& b)
{
if (a[0] < b[0])
return true;
else if (a[0] == b[0] && a[1] < b[1])
return true;
else if (a[0] == b[0] && a[1] == b[1] && a[2] < b[2])
return true;
else //这里的else return false非常重要!!!
return false;
}
int main()
{
int target, n, x;
cin >> target >> n;
vector<int> a;
vector<vector<int>> res;
for (int i = 0; i < n; i++)
{
cin >> x;
a.push_back(x);
}
//寻找三元组
res = threeSUM(a, target);
//输出三元组
sort(res.begin(), res.end(), comp);
for (auto line : res)
{
cout << line[0] << " " << line[1] << " " << line[2] << endl;
}
return 0;
}