开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情
Day03 2023/03/01
难度:中等
题目1
一个小球从10处落下,每次的弹回之前的高度一半,请问小球在静止之前一共走过多少距离?
题目2
“答案正确”是自动判题系统给出的最令人欢喜的回复。本题属于PAT的“答案正确”大派送——只要读入的字符串满足下列条件,系统就输出"“答案正确”,否则输出“答案错误”。
得到“答案正确”的条件是:
1.字符串中必须仅有P、A、T这三种字符,不可以包含其它字符;
2.任意形如PATx的字符串都可以获得“答案正确”,其中x或者是空字符串,或者是仅由字母A组成的字符串:
3.如果aPbTc是正确的,那么aPbATca也是正确的,其中a、b、c均或者是空字符串,或者是仅由字母A组成的字符串。
现在就请你为PAT写一个自动裁判程序,判定哪些字符串是可以获得“答案正确”的。
输入格式:
每个测试输入包含1个测试用例。第1给出一个正整数n(≤10),是需要检测的字符串个数。接下来每个字符串占一行,字符串长度不超过100,且不包含空格。
输出格式:
每个字符串的检测结果占一行,如果该字符串可以获得“答案正确”,则输出YES,否则输出NO
运行实例
思路一
- 根据题目一,我们通常可以选择递归或者循环的方式,这里我选择循环的方式,因为递归的空间复杂度会更高一些。本题比较简单,就不写详细的步骤了,代码注释写的比较详细,仔细看一下应该就没问题了!!!
思路二
题目二稍有难度,我在传统解法的基础上,优化了一下代码数量减掉了不少,看起来比较简洁,但是代码的理解难度也会随之提高一些,需要稍微琢磨一下。下面我就从题目开始一步一步分析:
- 首先题目中的条件1和条件2比较好理解,综合一下就是字符串中仅含
P,A,T这三种字母,并且最短且符合条件的字符串为PAT,像PT这样的就不符合条件。 - 条件三相比就更难理解一些,接下来细讲条件三:
- 条件三说如果aPbTc是正确,很多人就不明白"如果"了,这就用到条件二了,因为条件二可以确定100%答案正确的字符串。由条件二可以知道像PAT APATA AAPATAA AAAPATAAA ……都是正确的所以我们由条件二和条件三可以推出一些规律。
- 接下来我用条件三拿①PAT举个例子,重点来了:如果aPbTc=PAT,那么a=空,b=A,c=空,带入aPbATca得PAAT正确,如果PAAT正确,那么令PAAT=aPbTc,得a=空,b=AA,c=空,带入aPbATca得PAAAT正确,依次带下去可得PAAAA……T都是正确的。
- 下面找一下规律。
第一个数字代表P之前A的个数,第二个数字代表P和T之间A的个数,第三个数字代表T之后A的个数,观察可得:第一个数字*第二个数字=第三个数字。(核心观点)
关键点
- 题目二的代码中最后的
if判断条件比较多下面解释一下:c == '\n'保证了T后面没有其他字母,pos==2保证了PT之间没有其他字母,其中第一个if中else break保证了P前面没有其他字母,三个条件综合保证了一定符合条件一和条件;record_A[1]!=0,保证PT之间一定存在字符A,最后record_A[2]==record_A[1]*record_A[0]保证了符合上面找到的规律(即满足条件三)
算法实现
c++代码实现-优化后
#include <cstdio>
#include <iostream>
using namespace std;
// 自动判定输入的字符串是否符合正确答案要求
void AutoDetermine() {
int num = 0; // 待判定字符串个数
cin >> num; // 输入n值
char c; // c用来获取当前待判定字符串中的字符(单个单个的获取)
while (getchar() != '\n'); // 吸收掉n之后的第一个回车
for (int i = 0; i < num; i++) // 循环判定每num个字符串是否符合要求
{
int pos = 0, record_A[3] = {0, 0, 0}; // pos为record_A数组当前下标 record_A用来记录字符A在三个不同位置出现的次数
while (( c = getchar()) != '\n') // 判定输入的字符,用来记录各个A在不同位置出现的次数
{
if (c == 'A') record_A[pos]++; // record_A[0] P之前的A的数量
else if (c == 'P' && pos == 0) pos=1; // record_A[1] P与T之间的A的数量
else if (c =='T' && pos == 1) pos=2; // record_A[2] T之后的A的数量
else break; // 不满足条件一直接跳出循环
}
if(c == '\n'&& pos == 2 && record_A[1] && record_A[2] == record_A[1]*record_A[0]) // 在关键点处给出了详细对条件的解释,不懂得可以回头看一下
cout << "YES" << endl;
else cout << "NO" << endl;
if(c!='\n') while(getchar() != '\n'); //当不满足的时候吸收掉下一个回车,防止下次循环直接跳过
}
}
int main() {
cout << "题目1测试:" << endl;
float high = 10, second = 0, sum = 0; // high为小球每次即将落下时的高度,second为每次弹起的高度,sum为小球做过的距离和
while (high) { // 当high为0时停止循环
second = high / 2; // 每次弹起的高度为每次落下高度的一半
sum += (high + second); // 做累加
high /= 2; //更新high值
}
cout << "小球静止前一共走过:" << sum << "米" << endl << endl;
cout << "题目2测试:" << endl;
cout << "请输入待判定字符串的个数和待判定的字符串,每个数据各占一行,用回车隔开:" << endl;
AutoDetermine(); // 调用
}
题目一:
- 时间复杂度 --- n为最终循环的次数
- 空间复杂度 --- 仅有常数级的变量,没有额外的辅助空间
题目二:
- 时间复杂度 --- 外层循环n次,内层每次判断一个字符最坏情况下循环m次,其中n为字符串个数,m为字符串长度
- 空间复杂度 --- 仅有常数级的变量,没有额外的辅助空间
总结
- 题目二有一定难度,主要是条件三的分析,其次按照传统的写法,代码量会比较大,上述的方法经过优化过后代码量已经减少了很多,虽然看起来比较简洁,但是代码中很多细节点,其中关于'
/n'的细节点,最好是自己dbug一下去看代码是如何运行的。