第一次博客:PTA题目集1-3总结

2023-05-06,,

             第一次博客PTA题目集1-3总结

  前言:JAVA是一门非常好的语言,因其面向对象的思想,在解决问题时思路与上学期学习的C语言截然不同,但是其优势也是显然易见的,特别是在写大型程序时其面向对象的思想,可以让人思路清晰。

 这次PTA中三个“菜单计价”的题目让刚刚学习JAVA的我感到无力,但是在一阵磕磕碰碰之后终于还是解决了问题,以下是对题目的一些分析与总结:这几次的题目量以及难度按老师的说法是和上一届比简单了许多,但是突然使用一种新的编程语言和编程方式进行编程还是相当困难的。

题目概述:

第一次:虽然有9题但是总的来说题目不算难并没有用到“类”的思想,其主要目的是让我们熟悉JAVA语言的基本使用,以及各种类库的基本使用。

以下是各题的知识点考察:

  知识储备:1.使用Scanner输入以及System.out输出 2.不同类型的变量的定义以及输入 3.类库的简单使用。

  7-1 身体质量指数(BMI)测算  7-2 长度质量计量单位换算  7-4 房产税费计算2022  7-5 游戏角色选择:考察简单的输入(注意:输入为浮点型)以及条件判断(if—else)和输出。

  7-3 奇数求和:考察在循环中进行输入和判断以及求合操作。

  7-6 学号识别  7-9 二进制数值提取:考察(String)字符串操作类(substring,length,charAt)的使用。

  7-8 巴比伦法求平方根近似值:考察(Math)类(abs)求绝对值的使用的使用。

  7-7 判断三角形类型:考察判断的嵌套(分好类可以打的很漂亮)以及浮点类型数比较(需要设定精度)。

第二次:从这次开始上难度了!!!蔡老师开始向你展示JAVA的限额,但是困难总是伴随着成长,那些打不倒你的,只会使你变的更加强大;

  知识储备:1.对类与方法的基础使用 2.日期类的简单使用 3简单算法的使用 4类型转换。

    这里可以窥见面向对象思想的雏形,特别是“菜单计价1,2”以及“7-3 jmu-java-日期类的基本使用”,还有那阴间的测试点折磨的我痛不欲生,还好我是      一个阳光开朗大男孩,在连续问了七八个大佬后终于还是做出来了。下面简单分析7-3,7-4 剩下两个为菜单系列后面会统一分析

  7-3 jmu-java-日期类的基本使用

    难度:中等。

  考察对JAVA日期类的使用(Date、DateFormat、Calendar)都可以,其实真正难的是这题的类需要自己进行设计对于一个JAVA小白来说这简直就是”难于上青天“,且因为阴间的测试点,即使你的代码过了所有的测试点(例如:2022-10--1,如果使用字符串分割函数就会少一位导致后续判断失误)但是可能提交后一分也拿不到,而因为当时没有学习正则表达式,所以对输入的正确的判断变的十分困难——需要判断长度,是否为数字以及时间是否合法为此我还特意写了一个类。下面是我使用的(Calendar)以及正则表达式。

class 判断日期
{
Calendar c = Calendar.getInstance();//父类引用指向子类对象,右边的方法返回一个子类对象
void 日期(int year,int month,int day)
{
  c.set(year,month-1,day);//设置时间
  int a=c.get(Calendar.DAY_OF_YEAR);
  int b=c.get(Calendar.DAY_OF_MONTH);
  int d=c.get(Calendar.DAY_OF_WEEK);
  //星期天为1
  if(d==1)
  d=7;
  else
  d-=1;
  System.out.printf("%04d-%02d-%02d是当年第%d天,当月第%d天,当周第%d天.\n",year,month,day,a,b,d);
// 2021-02-28是当年第59天,当月第28天,当周第7天.

}
}

class 比较日期大小
{
Calendar c1 = Calendar.getInstance();//日期1
Calendar c2 = Calendar.getInstance();//日期2
void 比较(int year1,int month1,int day1,int year2,int month2,int day2)
{
c1.set(year1,month1-1,day1);
c2.set(year2,month2-1,day2);
int days = ((int)(c2.getTime().getTime()/1000)-(int)(c1.getTime().getTime()/1000))/3600/24;
System.out.printf("%d-%02d-%02d与%d-%02d-%02d之间相差%d天,所在月份相差%d,所在年份相差%d.\n",year2,month2,day2,year1,month1,day1,days,month2-month1,year2-year1);
//2020-01-02与2019-08-01之间相差154天,所在月份相差-7,所在年份相差1.
}
}

注:输入月份要-1,因为(calendar)的月份是从第0月开始的,以及其星期天为第1天,所有需要进行转换操作

正则表达式:

String timeRegex2 = "([-9][}[91[0-9[2{}[-91][0-9]}[0-9161][919-9121[910913(1((([5[18101(191][201013[01]))|*+"(146911)1)(01[0912]2101010)(2()01091010~1010)((-9]2})(0[48][2468]048][13579]126])[+(181][4161481][35791]3]0)))02\29)$";

//写的有点麻烦不知可否简化

注:使用这个两行就可以解决问题

  7-4  小明走格子:

    难度:中等。

这题找到规律其实计算并不困难(走到第n格的方法数等于走到前四格的方法数的总和)我使用了递归,但是难的是解决运行超时的问题,这让我想到可以使用记忆化搜索(其原理为将之前的运算结果进行储存,使函数不会进行重复计算);

  

 //b用于储存数据
    static int[] b=new int[10009];
    //d用于记忆化搜索//1为以有数据
    static boolean[] d=new boolean[10009];

    //当d[n]为true时说明a[n]已经有数据不用重复进行运算

    static int dfs(int x)
    {
    if(x==0)
      return 1;
    else if(x==1)
      return 1;
    else if(x==2)
      return 2;
    else if(x==3)
      return 4;
    else if(x==4)
      return 8;
    else
    {
      if(d[x])
        return b[x];
      else
      {
        d[x]=true;
        b[x]=dfs(x-1)+dfs(x-2)+dfs(x-3)+dfs(x-4);
        return b[x];
      }
    }
}

但是遗憾的是即使使用了记忆化搜索还是有一个测试点不能通过(出题老师把运行时卡的太死了),无奈只能求助于网络,其原因为使用(scanner)进行输入效率很低,应当改用(BufferedReader)进行输入,下面附上相应代码;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
int n=0;
try {

  n = Integer.parseInt(in.readLine());
}

catch (Exception e)

{
  e.printStackTrace();
}

注意:确实非常麻烦但是其效率是(scannre)的两倍,以后有运行时问题可以考虑使用。

第三次:这次可谓是时间紧任务重,6天7题实在是有点困难不过相对与上一次这次每题(第1题除外)的平均代码量远小于上一次,其主要考察方向为集合以及各种方法的使用(即运行时问题为主要问题),做的时候一定要切记不能使用暴力求解!!!像我这样一个上学期C语言算法没有好好学的就深受其害。所有的这次的方法都是网上即学即用,只能说:CSDN YYDS。

知识储备:1.对集合有基本认识(没有学习的话会带上痛苦面具) 2.一些算法基本知识(所有语言的算法都是互通的) 3.对封装性有基本认识(类,属性,方法的公有与私有)。

7-2 有重复的数据:使用双循环明显是不明智的,这里建议使用sort或MAP判重 因为只需要判断是否重复不需要将原输入进行处理后输出所以无论是使用那一种都十分的方便。下面是我使用的sort 其原理为将数组使用二分法进行快速排序,之后只需要判断每一个元素与其下一个元素是否相等即可(一重循环)。

import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args)
{
  Scanner in = new Scanner(System.in);
  int n;
  数据处理 b=new 数据处理();
  n=in.nextInt();
  int[] a=new int[n];
  boolean flag=true;
  for(int i=0;i<n;i++)
  {
    a[i]=in.nextInt();
  }
  Arrays.sort(a);//排序
  b.排序(a);
  b.查重(a);
  if(flag)
  {
    System.out.println("NO");
  }
  else
  {
    System.out.println("YES");
  }
}
}
class 数据处理
{
  int[] 排序(int[] a)
  {

  Arrays.sort(a);

  return a;
  }
  boolean 查重(int[] a)
  {

  for(int i=0;i<len-1;i++)
  {
    if(a[i]==a[i+1])
    return false;
  }
  return true;
}
}

看似完美但是我忽略了一个重要的问题(内存),于是我带上了痛苦面具,在一番深思熟虑后我发现了问题所在每次无论是使用“数据处理”中的 “排序” 还是“查重”方法都要开一个与a[]等大的数组导致内存超限。于是乎我将 “数据处理”类并入main中解决了这因为画蛇添足导致的问题。

import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args)
{
  Scanner in = new Scanner(System.in);
  int n;
  // 数据处理 b=new 数据处理();
  n=in.nextInt();
  int[] a=new int[n];
  boolean flag=true;
  for(int i=0;i<n;i++)
  {
    a[i]=in.nextInt();
  }
  Arrays.sort(a);//排序
  for(int i=0;i<n-1;i++)
  {
    if(a[i]==a[i+1])
    {
      flag= false;
      break;
    }
  }

  if(flag)
  {
    System.out.println("NO");
  }
  else
  {
    System.out.println("YES");
  }
}
}

          事实证明不要整花活!!!

7-3 去掉重复的数据:这题就是7-2plus,要求去除重复数据并输出,明显是不能使用sort了,但是观察数据为数字所以可以考虑使用数组标记法(多开一个标记数组,输入a[i],b[a[i]]++,这样子只需要检测b[a[i]]是否为1,即可判断是否需要进行输出 )但是容易爆内存。所以这里我选择使用LinkedHashSet为什么不能使用Set呢???因为题目要求按原来的数据进行输出而Set,HashSet内存放的数据都是无序的(与输入顺序不同)而LinkedHashSet内部的数据是有序的(与输入顺序相同)下面是基本使用方法。

  

//定义

Set<String> set = new LinkedHashSet<String>();

//添加元素

set.add(a);

//使用迭代器输出

Iterator<String> iter = set.iterator();

while(iter.hasNext()){
System.out.print(" "+iter.next()); //输出是有序的
}

7-4 单词统计与排序 一个典型的自定义排序问题,但是在输入处理这块就把我难住了,同时使用"," " " "."进行字符串分割,直接a=in.nextLine();String[] arr = a.split(" |,|.");当两个分割标志同时出现时会有多余字符出现如输入样例中同时有“,和 空格”会导致数组中会有一个空格多余而导致大错误。所以我先使用空格进行分割,之后使用循环遍历所有字符去除"."和","(注:千万不能先排序,然后在输出的时候去除"."和",",这样子的话"hello."将会和"hello"被视为相同的字符串)某个大聪明(指我自己)犯了这种低级错误。

如果是C语言的话我会用sort重构compare函数进行排序然后进行去重,当然JAVA也可以怎么做,但是我想试试集合TreeSet重构排序解决问题。下面是ThreeSet的简单使用以及重载函数的写法

//定义:

Set<String> set =  new TreeSet<String>(new SortDescending());//使用自定义排序

//添加元素

set.add(b);

//重载比较方法

class SortDescending implements Comparator //自定义排序
{
@Override//重载方法标志
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
if(s1.length()>s2.length())//按长度比较
return -1;
else if(s1.length()<s2.length())
return 1;
else //长度相等
{
return s1.compareToIgnoreCase(s2);//按字典序比较//不分大小写
}
}

}

7-5 面向对象编程(封装性):考察构造方法的使用,难度偏低,唯一要注意的点是:写了有参构造方法会覆盖掉原来自带的无参构造方法,所以需要再手写一个无参构造方法以保证可以使用无参构造方法构造对象

7-6 GPS测绘中度分秒转换:这个题目主要考察格式化输出,但是如果全部采用格式化输出 "秒" 将不能保持与输入位数相等,所以解题思路为“度”“分”“秒”使用普通输出方式进行输出(print),结果使用格式输出(printf)以控制6位小数。

7-7 判断两个日期的先后,计算间隔天数、周数:这题的方法在 第二次作业7-3 jmu-java-日期类的基本使用 中讲过这里就不再赘述了,但是有一点值得注意的是如果使用的是(Date、DateFormat)类进行求解,因为其时间输入格式要求所以应当将输入转换为统一格式(2022-6-6>>2022-06-06)。

菜单系列 

  这是蔡老师对我们全体学生的爱,麻婆豆腐 12 油淋生菜 9 想必所有同学都以及烂熟于心了吧。按老师的说法,这比上一届的三角形简单一些,而且蔡老师贴心的为我们先写好了框架,我们最重要做的是对类中方法的调度,作为一个初学JAVA的新手,这过于复杂的题目曾一度让我想要放弃,无数次非零返回,答案错误,段错误,让我带上了痛苦面具,不过在鏖战了几天几夜之后我还是勉强将其做了出来。下面我将一一介绍这三座大山的不同难度以及特点。

知识储备:1.对JAVA类初步认识 2.类的设计 3.类中方法的设计 4.对象的使用

菜单计价程序-1 这是这个系列的第一题难度当然是比不上后面俩题,但是正所谓万事开头难,这复杂的类间关系,和我那浅薄的知识量,CPU烧了!!!不过通过这题我对JAVA类的创建和调度已经算是初窥门径,所有没有压力就没有动力,人的潜能是被逼出来的。

  过程非常的简单用户输入点菜,计价,输出,计算总价,要是不用面向对象的思想,使用面向过程的方法我很快就可以将其写成,但是使用面向对象我就不行了,下面我简单介绍一下我认为得各个类的作用和类间的调度与交流。

1.Dish 菜品类:对应菜谱上一道菜的信息。没有在主类中直接使用,但是菜单和点菜记录都要依赖于它,其中的getPrice(int portion)方法是于点菜记录中的getPrice()方法对应的。

2.Menu  菜谱类:对应菜谱,包含饭店提供的所有菜的信息。内有一个Dish类的对象数组用于存菜单内的菜,以及一个Dish类型的方法 searthDish(String dishName)用于配合点菜记录进行查找比对,并返回这道菜(内含有该菜的价格)用于后续计算

3.Record :点菜记录类:保存订单上的一道菜品记录,配合Menu类里的searthDish(String dishName)和Dish类里的getPrice(int portion)计算菜品的价格

4.Order:订单类:保存用户点的所有菜的信息。 内含Record数组用于储存订单信息,getTotalPrice()/方法/计算订单的总价,addARecord(String dishName,int portion)用于输入

大体流程:循环输入以“end”作为结束标记,使用Order类的addARecord(String dishName,int portion)方法存入订单中,使用循环遍历订单并使用Menu中的 searthDish(String dishName)方法进行比对做出相应的输出,最后使用Order类的getTotalPrice()计算总价。

一些技巧:1.计算要求四舍五入,但是对于本题没有可以直接对“西红柿炒蛋 15”和 “油淋生菜 9”进行特殊处理,即计算结果加1;

     2.在点菜时点到菜单没有的菜时searthDish(String dishName)可以返回null,然后通过判断返回值进行相应输出,也可以在返回null前进行输出“菜不存在”。

菜单计价程序-2这比菜单1多加了菜单写入,以及删菜的功能,如果上一题,打的思路清晰,明明白白的话这题就不会很难,由于很多重复的地方所有这里仅讲解不同的部分。

2.Menu  菜谱类:对应菜谱,包含饭店提供的所有菜的信息。内有一个Dish类的对象数组用于存菜单内的菜,addDish(String dishName,int unit_price)方法用于将输入的菜品存入菜单,以及一个Dish类型的方法 searthDish(String dishName)用于配合点菜记录进行查找比对,并返回这道菜(内含有该菜的价格)用于后续计算。

4.Order:订单类:保存用户点的所有菜的信息。 内含Record数组用于储存订单信息,getTotalPrice()/方法/计算订单的总价,addARecord(String dishName,int portion)用于输入,delARecordByOrderNum(int orderNum)用于删除订单内的菜品。

大体流程:循环输入以“end”作为结束标记,将输入进行分类,创建菜单时使用Mneu类的addDish(String dishName,int portion)方法储存菜单信息,点菜时使用Order类的addARecord(String dishName,int portion)方法存入订单中,使用循环遍历订单并使用Menu中的 searthDish(String dishName)方法进行比对做出相应的输出,删菜时使用Order类中的delARecordByOrderNum(int orderNum)方法进行删除并做出相应删除,最后使用Order类的getTotalPrice()计算总价。

一些技巧:1.输入后使用字符串分割函数进行分割(String[] arr = a.split(" ");),之后使用(arr.length)判断数组内元素个数,若为4则为点菜,若为2则判断第二个元素是否                        为delete若是则为删菜,否则为加菜到菜谱中。

       2.计算要求四舍五入(不能像上面那样投机取巧)因只有五入所以可以直接向上取整((int)Math.ceil(1.0*unit_price*3/2);。

       3.删除菜品的时候因为点菜都有编号(且为递增的)所以可以直接与订单数进行比较判断该删除序号是否存在

       4.删除菜品的时候可以返回菜品价格之后在计算总价的时候减去,也可以直接将订单中该菜的份数(或价格)改为0。

       5.遍历菜单时倒序遍历可以保证读到的是最新的菜信息

 菜单计价程序-3在2的基础上加入了,桌,以及待点菜,还有营业时间的判定。感觉一下子就复杂起来了,而且桌类需要我们自己设计,吐槽一下:这题的题目有问题,结束时间应该是21:30,题目上写的是21:00搞的我想跳楼。其他确实没有什么太大的变化,主要就是桌类的设计。下面简单介绍一下我的设计思路。

 

Table:桌类需要有这一桌的所有信息,所以属性有(

int tablenum;//桌号

String time;//点菜时间
int year=0,month=0,day=0,ww=0,hh=0,mm=0,ss=0;
boolean flag=true;//判断时间是否正确
double count=0;//折扣
Order selforder=new Order();//本桌订单

),而方法有对时间的处理,判断营业与折扣,计算总价。

大体流程:首先创建一个Table类的数组,循环输入以“end”作为结束标记,将输入进行分类,创建菜单时使用Mneu类的addDish(String dishName,int portion)方法储存菜单信息,加桌的时候将信息存入Table类的数组中的time属性中,点菜时使用Table对象中Order类的addARecord(String dishName,int portion)方法存入订单中(待点菜与其相同),使用循环遍历订单并使用Menu中的 searthDish(String dishName)方法进行比对做出相应的输出,删菜时使用Order类中的delARecordByOrderNum(int orderNum)方法进行删除并做出相应删除,最后使用Table对象的getTotalPrice()计算总价并做出相应输出。

一些技巧:1.判断是否在营业时间时,可以将Table类中中的count//折扣初始化为0,首先计算折扣,若折扣为0则判断为不在营业设计。

     2.四舍五入sum=(int)(sum*count+0.5);用这个就可以了

下面附上我的折扣计算:

void jscount()//运用时间计算折扣
{
  if(ww>=1&&ww<=5)
  {
    if(hh>=17&&hh<20)
      count=0.8;
    else if(hh==20&&mm<30)
      count=0.8;
    else if(hh==20&&mm==30&&ss==0)
      count=0.8;
    else if(hh>=11&&hh<=13||hh==10&&mm>=30)
      count=0.6;
    else if(hh==14&&mm<30)
      count=0.6;
    else if(hh==14&&mm==30&&ss==0)
      count=0.6;
  }
  else
  {
    if(hh>=10&&hh<=20)
      count=1.0;
    else if(hh==9&&mm>=30)
      count=1.0;
    else if(hh==21&&mm<30||hh==21&&mm==30&&ss==0)
      count=1.0;
}

菜单系列踩坑心得(这世上本没有坑,踩的人多了就成了坑)

     1.可以去elicpice上测试,但是搞完复制回PTA一定一定要把package删了,以及主类改为Main,会非零返回。

    2.使用数组元素时一定要先new

    3.对象第一次new了之后,无论在那都不要再new了不然会变成null

    4.访问数组不能越界,会有段错误

    5.只要不是void类型的方法一定要保证所有情况都有返回值,会非零返回

    6.不能在定义属性的地方使用方法,如7-1中对菜单的初始化

    7.一些超时可能是圈复杂度过高,或循环没有出口下面是我的第三个菜单的圈复杂度

  

  

  8.7-3一定要注意方法的重复执行(对同一道菜算了两次单价),否则会运行超时,好的解决方法是,在Table类中加入sum属性用于计算总价(每次算完单价后直接加)

t[i-1].selforder.addARecord(dd1, dd2, arr[2], dd3,dd4);
g=c.searthDish(arr[2]);
if(g!=null)
{
t[i-1].selforder.records[k].d=g;
int x=t[i-1].selforder.records[k].getPrice();
//4 table 2 pay for table 1 12
System.out.println(dd2+" table "+i+" pay for table "+dd1+" "+x);
t[i-1].sum+=x;
}
k++;

 总结:

  通过这三次PTA大作业,我深刻感受到了,面向对象与面向过程的差别,对类有了一定的认识,题目都不算特别难,但是每一个题目都是一个知识点,特别是菜单系列刚开始的时候无从下手做完后再看看感慨万分,虽然类是老师设计的,但是其中的交流是自己打的,熬了几天大夜虽然很累但是拿满分的那一刻成就感满满。

学到了什么:

  1.对类的设计有了一定的了解。

  2.对集合有了一定的认知。

  3.体会到优秀的框架设计的好处(优秀的架构与细节设计。优秀的含义很广泛,清晰、可读、易维护、易扩展的设计往往是鲁棒的,这样的设计往往很难出现严重的纰漏,因为很多错误已经被规避掉了,即使有错               误,发现、纠正的难度也不会太大

  4.对JAVA报错的修改更加得心应手。

  5.对类的封装性有了概念

    6.学习了正则表达式的基本使用

 对课程的建议

       1.每次作业截止之后可以出一下不计分的补题,可以让没有及时解决问题的同学继续尝试。

                

第一次博客:PTA题目集1-3总结的相关教程结束。

《第一次博客:PTA题目集1-3总结.doc》

下载本文的Word格式文档,以方便收藏与打印。