PTA数组及排序查找题解与解题思路
PTA数组及排序查找题解与解题思路
函数题目
函数题目为平台提供的裁判程序调用所完成的函数进行判题,题目规定语言为C语言
6-1 求出二维数组的最大元素及其所在的坐标
本题较为简单,考察的是如何遍历一个二维数组,只需要两个循环依次遍历其每个维度和元素即可
如何寻找最大值?
只需要在遍历每个元素的过程中,使用一个变量记录最大值,当出现更大的值时,更新最大值的变量即可,同时更新最大值所在的坐标(题目已经给出的全局变量中已经定义,即Row与Col)
int fun(int array[N][M])
{
int max = -1;
for(int i=0;i<4;i++)//遍历每个维度
{
for(int j=0;j<M;j++)//遍历每个元素
{
if(array[i][j]>max)//如果找到了比当前最大值更大的值
{
//更新最大值及其下标
max = array[i][j];
Row = i;
Col = j;
}
}
}
//返回找到的最大值
return max;
}
6-2 求平均值并统计小于等于平均值的个数
本题依然是考察数组的遍历,只需要依次迭代和累加每个元素,求出所有元素的和然后求出平均值,然后再一次遍历每个元素,并且找出小于平均值的元素即可
int fun(float x[],int n)
{
float sum=0;//定义累加器
for(int i = 0 ;i<n;i++)//遍历每个元素
{
sum+=x[i];//将每个元素累加到sum中
}
float ave = sum / n;//求平均值并存入ave中
printf("ave=%.2f\n",ave);///根据题意打印平均值
int t=0;//定义一个遍历,用于存储小于平均值的数的个数
for(int i = 0 ;i<n;i++)//再次迭代每个元素
{
if(x[i]<ave) t++;//每找到一个小于平均值的元素,就让t自增1
}
return t;//返回小于平均值的元素个数
}
6-3 杨辉三角形
本题较难,主要考察对递推规律的总结:a[i][j]=a[i-1][j-1]+a[i-1][j]
即杨辉三角的每一个元素都等于其左上和右上两个元素的和
void fun(int a[N][N],int n)
{
int i,j;
for(i=0;i<n;i++) // 对每层循环,一共循环n层
{
a[i][0]=1; //每一层的第一个元素一定为1
a[i][i]=1; //每一层的最后一个元素也为1 ps:关于为什么最后一个元素为a[i][i]:因为杨辉三角的每次数字个数就等于层数+1,由于C语言中是以0开始计算元素下标的,因此第i+1个元素的下标就为i
for(j=1;j<i;j++)
{
a[i][j]=a[i-1][j-1]+a[i-1][j];//递推关系:杨辉三角的每一个元素都等于其左上和右上两个元素的和
}
}
}
6-4 歌唱比赛打分
本题依然是一个简单题,依旧考察的是数组的迭代,并且考察了从数组中寻找最大和最小值的方法
?关于为什么样例函数中第一个参数为指针而不是数组:
实际上数组本身在内存中即为一段连续的内存,而数组名称则表示数组第一个元素在内存中的地址,因此事实上,数组名就是一个特殊的指针,指向数组第一个元素的地址,而对于访问数组中的每一个元素,实际上就是访问从首地址出发的第n个数据类型大小的内存地址中存储的值=,因此存在这个恒等式
nums[T]=*(nums+T)
同样的,在声明函数时,我们既可以写成
double getScore(int *score, int total)
,同样也可以写作double getScore(int[] score, int total)
,这二者之间完全一样还有一个例子,就是在scanf函数读取字符串(%s)的时候,用于接收字符串的数组名不需要加&取地址符号,因为数组名本身就指代一个内存地址
如果你想了解更多,可以搜索
C++中数组的底层原理
double getScore(int *score, int total)
{
int max = 0, min = 100, sum = 0; //声明最大值,最小值和累加器
for(int i = 0; i < total; i++)
{
if(score[i] > max) //如果发现一个比当前最大(小)值更大(小)的数,就更新当前最大(小)值
max = score[i];
if(score[i] < min)
min = score[i];
sum += score[i];//将累加每一个分数求和
}
sum -= max;//从总分中减去最高和最低分
sum -= min;
double score_avg = (double)sum / (total - 2);//计算剩余的平均值并且返回
return score_avg;
}
6-5 数据排序
本题目为一个中等题,主要考察的是排序算法,这里列出几种常见的排序算法,仅对其中的部分算法解释,如果感兴趣可以去百度其他算法的原理
冒泡排序
冒泡排序顾名思义就是迭代n次数组,每次从0开始到数组长度-1,如果对于迭代的两个数有an>a(n+1),则交换两者的数据,以保证最大的数就像泡泡一样冒出水面
时间复杂度O(nlogn)
void fun(int a[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
快速排序
快速排序是一种基于分治算法的排序方式,效率较高,这里仅举例,原理可百度
时间复杂度O(logn)
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
编程题
7-1 求最大值及其下标
本题要求编写程序,找出给定的n个数中的最大值及其对应的最小下标
解题思路:实际上本题还是只要求n个数的中最大的数与其对应的最小下标(其实就是第一个最大的数是第几次输入的),事实上,本题并不需要使用数组来完成,直接对每次输入进行比较即可,因此这里给出两种方案
AC Code C++ (使用数组版本)
int main() {
int n, max_index;
cin>>n;
int nums[n];
for (int i = 0; i < n; i++) cin>>nums[i];//输入n个数,如果是C语言可以写成scanf("%d",&numsp[i]);
int max_value = nums[0];//假定第一个数为已知的最大数
max_index = 0;//设置最大数的下标
for (int i = 1; i < n; i++) {
if (nums[i] > max_value) {//如果存在一个比当前最大数还大的数,则更新最大数和对应下标
max_value = nums[i];
max_index = i;
}
}
printf("%d %d\n", max_value, max_index);//输出
return 0;
}
AC Code C++ (不使用数组版本)
int main() {
int n, max_index;
cin>>n;
int max_value = -99999;
max_index = -1;
for (int i = 0; i < n; i++)
{
int temp;//设置一个临时变量用来存储输入的值
cin >> temp;
if(temp>max_value)//如果存在一个比当前最大数还大的数,则更新最大数和对应下标
{
max_value = temp;
max_index = i;
}
}
printf("%d %d\n", max_value, max_index);
return 0;
}
7-2 字符串逆序
本题考查字符串的操作,属于简单题,但是输入部分略有不同
? 因为其输入为一行字符串,可能包含空格,因此无法使用传统的scanf或者cin读入,但是在C语言中,可以使用gets函数,C++中可使用getline或者cin.getline来读入,同时我们需要使用strlen函数来获取字符串的实际长度,用于倒序输出字符串
AC Code (C语言版本)
#include <stdio.h>
#include <string.h>
int main()
{
char s[81];
gets(s);//读入一整行字符串
for(int i = strlen(s)-1;i>=0;i--)//从s的最末尾循环到开头并且一次输出每个字符
{
printf("%c",s[i]);
}
}
AC Code (C++版本)
#include <bits/stdc++.h>//C++通用头文件
using namespace std;
int main()
{
string s;//C++中有专门的字符串类,用于存储字符串
getline(cin,s);//读入一行字符串到s中
reverse(s.begin(),s.end());//C++中,自带reverse函数可以将字符串翻转
cout<<s;//直接输出反转后的字符串即可
}
AC Code (Python3版本)
print(input()[::-1]) //[::-1]可以直接将输入的数据逆序,然后使用print直接输出即可
Ps:人生苦短,我用Python
一行就秒杀了,C他做的到吗(大雾
7-3 求矩阵各行元素之和
本题看上去是一个二维数组的题目,实际上不需要用到二维数组,我们求和仅需要用到一维数组即可(甚至连数组都不要用),我们只需要迭代二维数组的每个维度,把二维矩阵看作n个一维数组,然后每次对一维数组求和直接输出答案即可,因此本题实际上考察的还是数组的迭代,属于简单题
AC Code C++使用数组版
int main() {
int n,m;
cin >> n >> m; //C语言改成scanf("%d%d",&n,&m);
int p[1000]; //定义一个数组(只要够大就行)用来存储每个一维数组的和
memset(p,0,sizeof p);//把数组的每个元素重置为0,如果定义为全局变量的话数组就会默认清空,可以忽略这一步
for(int i= 0 ; i < n ; i++)//迭代二维数组的每个维度
{
int sum = 0;//类和变量
int temp;//一个临时变量,用来存储输入
for(int j = 0;j<m;j++)//遍历当前二维数组的当前维度(直接看成一个一维数组即可)
{
cin >> temp;//输入,C语言改成scanf即可
sum+=temp;//将输入的值存入temp
}
p[i]=sum;//把和存入p中
sum = 0;//清空类和,准备存储下一个维度
}
for(int i = 0 ; i < n;i++) //循环p数组中存储的每个维度的和并输出即可
cout<<p[i]<<endl; //C语言改成printf
}
AC Code 不用数组优化版
int main() {
int n,m;
cin >> n >> m; //C语言改成scanf("%d%d",&n,&m);
for(int i= 0 ; i < n ; i++)//迭代二维数组的每个维度
{
int sum = 0;//类和变量
int temp;//一个临时变量,用来存储输入
for(int j = 0;j<m;j++)//遍历当前二维数组的当前维度(直接看成一个一维数组即可)
{
cin >> temp;//输入,C语言改成scanf即可
sum+=temp;//将输入的值存入temp
}
//p[i]=sum;//把和存入p中
cout<<sum<<endl; //直接输出sum即可,C语言改成printf("%d/n",sum);
sum = 0;//清空类和,准备存储下一个维度
}
}
7-4 交换最小值和最大值
本题考察的任然是数组迭代,我们需要找出最小值和最大值并且交换他们到数组的队头和尾,本题看似简单,实际上暗藏玄机,很容易犯思维的错误
错误代码示例 Wrong Answer Code
int main() {
int n;
scanf("%d", &n);
int nums[n];
for (int i = 0; i < n; i++) {
scanf("%d", &nums[i]); //读入数组
}
int min_index = 0;//定义最大值和最小值的下标
int max_index = 0;
for (int i = 0; i < n; i++) {
if(nums[i] < nums[min_index])//更新最大和最小值的下标
min_index = i;
if(nums[i] > nums[max_index])
max_index = i;
}
swap(nums[min_index], nums[0]); //交换最小值到头部
swap(nums[max_index], nums[n-1]); //交换最大值到尾部
for(int i = 0; i < n; i++) //输出交换后的数组
printf("%d ", nums[i]);
printf("\n");
return 0;
}
输入
5
8 2 5 1 4
期望输出
1 2 5 4 8
实际输出
4 2 5 8 1
错误分析
其实本段代码的思路并没有错误,主要是逻辑上产生了错误,事实上,我们同时找出了最小值和最大值的下标,但是存在冲突情况,例如给出的样例数据中,8为最大值,处于队首,而我们先进行交换最小值到队头的时候,8已经被交换到了原来最小值的位置,而我们最大值的下标仍然指向8交换之前的位置(即队首位置),此时再交换最大值与队尾的时,我们交换的任然是原来最大值下标(也就是队首)与队尾的位置,但事实上最大值已经在第一次交换时换到了其他位置,所以产生了逻辑错误
原数组
8 2 5 1 4
第一次交换后(交换最小和队首)
1 2 5 8 4
第二次交换后(交换最大和队尾,最大值下标任然指向0,但实际上最大值8已经换到了原来1的位置)
4 2 5 8 1
正确的思路
因此,我们应该先找到最小值,交换完成最小值和队首之后,重新遍历,寻找最大值,再次交换最大值和队尾,这样方可避免逻辑错误
AC Code C语言版本
void swap(int &a,int &b) //交换函数实现,C语言没有自带的swap需要自己实现,C++自带这个函数,可以不用加此段代码
{
int temp;
temp = a;
a = b;
b = temp;
}
int main() {
int n;
scanf("%d", &n);
int nums[n];
for (int i = 0; i < n; i++) {
scanf("%d", &nums[i]); //输入数据
}
int min_index = 0;
int max_index = 0;
for (int i = 0; i < n; i++) {
if(nums[i] < nums[min_index]) //首先查找最小值
min_index = i;
}
swap(nums[min_index], nums[0]); //交换最小值与队首
for (int i = 0; i < n; i++) {
if(nums[i] > nums[max_index])//在新的数组中重新查找最大值
max_index = i;
}
swap(nums[max_index], nums[n-1]); //交换最大与队尾
for(int i = 0; i < n; i++)
printf("%d ", nums[i]);
printf("\n");
return 0;
}
7-5 求整数序列中出现次数最多的数
这是一个简单题,但是上手时大部分人可能会考虑映射表来完成,但事实上,本题的测试点特意卡了数据范围,如果强行使用映射表来做,必定最后一个点会爆栈,所以我们如果要用映射表的话可以考虑使用哈希表,缩小映射范围,因此我们这里采用传统的遍历+统计方法即可轻松AC
AC Code C语言版本
#include<stdio.h>
int main()
{
int i,n;
scanf("%d",&n);
int a[n]; // 创建一个长度为n的整数数组a(虽然这么写能过编译,但事实上这是非法的,数组的长度必须是常量)
for(i=0;i<n;i++)
scanf("%d",&a[i]); // 循环读入每个整数到数组a中
int j,num,count,max=-1,maxnum;
for(i=0;i<n;i++)// 循环统计每个整数出现的次数
{
num=a[i]; //记录当前整数
count=0; //初始胡计数器
for(j=0;j<n;j++)// 循环统计当前整数出现的次数
{
if(a[j]==num)
count++;// 如果当前整数等于数组a中的某个整数,计数器加一
}
// 如果当前整数的次数大于最大次数,更新最大次数和最大整数
if(count>max)
{
max=count;
maxnum=a[i];
}
}
// 输出最大整数及其出现次数
printf("%d %d",maxnum,max);
return 0;
}
关于C++代码,如果不使用STL的哈希表,写法与C基本一致,因此不再过多赘述
AC Code Python3秒杀版本
nums = input().split() //获取输入
nums = nums[1:len(nums)] //去掉第一个元素,因为第一个元素代表数组长度,并不在实际的数据中
print(f"{max(nums, key=nums.count)} {nums.count(max(nums, key=nums.count))}")//直接统计出现次数最多的字符和出现次数
//以后兴趣可以查查python的用法,这里不再解释(毕竟是C的教程)
7-6 字符串字母大小写转换
本题实际上是考察你是否有看过C语言的标准库函数,如果你直到C语言自带的函数那么本题就是一个简单的数组遍历题
用到的函数
islower
本函数有一个参数,用来判断参数是否为小写字母,如果是则返回true,不是返回false
isupper
用法同上
toupper
本函数有一个参数,返回参数对应的大写字母,如果参数不是小写字母则返回0
tolower
用法同上
直接放上AC代码,看一遍就明白了
AC Code C/C++通用版
int main() {
char s[31];
cin.getline(s,31); //C语言改成gets(s);
for(char &ch : s) //这是C++特有的对元素遍历,C语言用正常的for循环就好,把下面的ch改成s[i]即可
{
if(islower(ch)) ch = toupper(ch); //如果是小写字母,就转化成大写
else if(isupper(ch)) ch = tolower(ch); //同理
else if(ch=='#') ch = '\0'; //如果遇到了#,说明到达了结尾,然而#是不需要输出的,因此直接把#改成\0即可
}
puts(s);//puts函数等价于printf("%s",s);
}
7-7 统计字符出现次数
事实上,在第五题之后这道题目可以直接用映射表来秒杀
?关于什么是映射表
映射表实际上是利用的数组的特性,也就是下标与元素相对应的关系,由于每个char类型本质上还是一个整数,因此可以直接定义一个整数数组来创建映射,例如a['M']则表示M字符出现的次数,利用这一特性,我们可以为每一个字符创建映射,如果我们想知道每个字符出现了多少次,直接访问a[char]即可,可以将时间复杂度降低到O(1)
AC Code C/C++
int main() {
int _map[1000]; //定义一个长度大一点的映射表
memset(_map,0,sizeof _map); //清空映射表,如果定义为全局变量则可以不要清空
char s[81]; //定义一个字符串
cin.getline(s,81); //读入一行到字符串,C语言看上面例题的写法
for(char ch : s) //同理,上一题中介绍了具体用法
{
_map[ch]++; //映射表对应元素自增
}
char m;
scanf("%c",&m);
cout << _map[m]; //直接输出m映射的值(即m的出现次数即可)
}
AC Code Python3秒杀法
s = input()
m = input()
print(s.count(m)) //count方法可以直接统计m出现的次数,直接输出即可
7-8 统计一行文本的单词个数
本题要我们统计单词出现的次数,只要连着的一个整体就算一个单词,单词之间以空格隔开,因此,我们换个思路,只要统计空格的个数,再加上1,就能得到单词的个数,但是出题人肯定不会这么蠢,事实上,题目中说明了空格可能是连续的,因此我们可以把重心放到如何跳过连续空格即可,关于空格,我们一定要考虑特殊情况,因为我们统计的是空格,输出的答案为空格数+1,此时便存在特殊情况,即字符串如果全是空格,不包含单词,此时应该做出特判,其次,我们还需要判断前导空格和后置空格,这些空格均为无效空格,只有在字符中间的空格才是有效空格,所以,具体实现看代码
AC Code C语言版本
#include<stdio.h>
#include<string.h>
int main()
{
char s[10000];
gets(s); //读入一个字符串
int sum =0;
int i=0;
while(s[i]==' ') i++; //如果前面存在空格,则让i一直自增,直到跳过所有的前置空格
if(i==strlen(s)) //此时做出特判,如果跳过了整个字符串,则说明整个字符串全是空格不含单词,直接输出0并且返回即可
{
printf("0");
return 0;
}
for(int j = strlen(s)-1;s[j]==' ';j--){ //从后方开始遍历,如果是后置空格,则改为字符串结束符号
s[j]='\0';
}
for(;i<strlen(s);i++)//此时剩下的字符串部分即为我们需要真正判断的部分
{
if(s[i]!=' ') continue;//如果不是空格则跳过
else
{
sum++;//如果是空格,则sum+1
while(s[i+1]==' ') i++;//跳过此后的所有空格
}
}
printf("%d",sum+1);//单词数量=空格数+1
return 0;
}
事实上,这种特判的方法开辟了太多的分支,也就是说我们将简单的方法复杂化了,那么如果我们从更加一般的角度入手呢,假设前后全部存在空格,那么我们只需要用空格数-1即为单词数量,但是并不是每一种情况都包含前导空格和后置空格,但题目是死的人是活的,我们可以自发的给字符串加上前导空格和后置空格,将其一般化,这样添加前导空格和后置空格的操作比我们去除前导空格和后置空格的操作要简单太多,看以下优化过的代码
AC Code C语言版本(优化版)
#include<stdio.h>
#include<string.h>
int main()
{
char s[10000];
gets(s+1); //修改s指针的位置,空出s[0],用来给我们放置前导空格
s[0] = ' '; //放置前导空格
s[strlen(s)]=' '; //将原来的结束标志改为后置空格
s[strlen(s)+1] = '\0'; //将结束标志的后一位改成结束标志
int sum =0;
for(int i=0;;i<strlen(s);i++) //这样我们直接用原来的方法统计空格,再减去1即为正确答案,这样的写法更加具有通用性和逻辑性
{
if(s[i]!=' ') continue;
else
{
sum++;
while(s[i+1]==' ') i++;
}
}
printf("%d",sum-1);
return 0;
}
AC Code Python3秒杀版
s = input().split() //输入字符串,并且按照空格为间断符号分割字符串
print(len(s)) //剩下的字符串数量即为单词数量,直接输出即可
7-9 查验身份证
看上去,本题确实非常复杂,但是实际上,考察的无非就是映射表和数组特性的应用,如果能够把本题看成多个小问题的集合,实际上问题也就迎刃而解了,因此,我们分大问题为以下几个小问题
- 身份证前17为是否都是数字
- 身份证前17为数的加权求和
- 最后一位数的合法性检查
一旦我们大问题分为几个小问题,我们的思路就变得清晰了,首先第一个问题非常好解决,我们只需要用到系统的函数isdigit
就能轻松解决
对于第二个问题,题目中已经给出了每个数的对应权重,我们直接利用数组的特性即可完成,具体实现看代码
第三个问题,我们已经有加权和的,只需要把和mod11得到的余数利用映射表,看余数映射的值是否等于最后一位校验码即可,至此,所有的关键问题我们均已解决
完整代码如下
AC Code C++版本
#include <bits/stdc++.h>
using namespace std;
int p[] = {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2}; //权重
char chk[] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; //映射表
bool isnums(string nums) //首先判断前17个数是否均为数字
{
for(int i = 0;i<nums.size()-1;i++) //已知每个身份证长度均为18位,因此遍历0到size-1即可
{
if(!isdigit(nums[i])) return false;//只要有一个不是数字,就直接返回false
}
return true;//如果他的每一位都通过了检查,那么返回true
}
bool check(string nums) //检验合法性
{
int sum = 0;
for(int i=0;i<nums.size()-1;i++)
{
sum+=(nums[i]^48)*p[i]; //首先我们计算每个数加权后的值,这里存在字符与数字的转换,这里巧妙的利用了位运算来实现,事实上任何字符异或48都等于其数字值,等价于'1'-'0'=1
}
return nums[17] == chk[sum%11]; //如果最后一位等于加权余数对应在映射表的值,说明其合法
}
int main() {
string* s;
int n;
cin >> n;
s = new string[n];
for(int i = 0;i<n;i++) cin >> s[i];
bool pass = true;
for(int i = 0;i<n;i++)
{
if(!(isnums(s[i].c_str())&&check(s[i].c_str()))) //对每个字符串都进行数组检查和合法检查,只要有一个没有通过,pass则为假,同时输出未通过的字符串
{
pass = false;
puts(s[i].c_str());
}
}
if(pass) puts("All passed");//如果pass任然为真,说明所有的字符串均已经通过,输出All Passed
}
这里我们利用了C++ String类方便的特性和C++优秀的语法特性,在后面我同样附上一份C语言的版本,供各位学习参考
AC Code C语言版本
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int p[] = {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};
char chk[] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
int isnums(char* nums)
{
for(int i = 0;i< strlen(nums)-1;i++)
{
if(!isdigit(nums[i])) return 0;
}
return 1;
}
int check(char* nums)
{
int sum = 0;
for(int i=0;i<strlen(nums)-1;i++)
{
sum+=(nums[i]^48)*p[i];
}
return nums[17] == chk[sum%11];
}
int main() {
char s[100][100];
int n;
scanf("%d",&n);
for(int i = 0;i<n;i++) scanf("%s",&s[i]);
int pass = 1;
for(int i = 0;i<n;i++)
{
if(!(isnums(s[i])&&check(s[i])))
{
pass = 0;
puts(s[i]);
}
}
if(pass) puts("All passed");
}