对Java课程的PTA三次集训的总结
前言:
集训一考察了基本的Java编程语法,基本的输入输出,选择结构,循环结构,强制类型转换,查找字符串中的特定位置的字符,字符的删去,数组的定义及其初始化,对数组元素的赋值,数组元素的查找,如何四舍五入,输出时对保留几位小数的控制,辗转相除法求最小公倍数。题量较多,但难度较小。但是也有较难的题,比如计算GPS 集训二考察了double型数据和float型数据的精确度区别,利用循环对数组数据进行处理,switch语句的用法,字符型数据转化为整型数据再进行处理,字符串,浮点型数据相等的操作,对日期的合法性的判断。题量不多,难度比集训一更大。 集训三考察了类的建立和类间关系,类的引用,用setter方法对类的属性进行改变,用getter方法获取类的属性,求日期的算法。题量少,但是难度较大,比如7-3和7-4,尤其是7-4,算法比较复杂,导致很难拿到分。
设计与分析:
集训一1-3打印九九乘法表:
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); int n = aaa.nextInt(); int i,j,k=1; if(n<1||n>9) System.out.println("INPUT ERROR."); else{ for(i = 1;i<=n;i++) for(j = 1;j<=i;j++){ if(j==i){ k = j*i; System.out.println(i + "X" + j + "=" + k); } else{ k = j*i; System.out.print(i + "X" + j + "=" + k +"\t"); } } } } }
首先定义Scanner输入一个整数,用if判断该数是否小于于9且大于1,如果不是则输出特定语句,是则进入双循环。通过这个题目,让我对于Java的循环语句有了更深的理解,同时增强了我的编码能力。
集训一7-5去掉重复的字符
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); String s = aaa.next(); StringBuffer ss= new StringBuffer(s); int i,j; for(i=0;i<ss.length();i++) for(j=i+1;j<ss.length();j++){ if(ss.charAt(i)==ss.charAt(j)){ ss.deleteCharAt(j); j--; } } System.out.print(ss); } }
先定义一个String字符串,输入需要查重的字符,然后将String类型转换为StringBuffer类型。这样的好处是,StringBuffer类型可以对字符串进行增删查改操作,而String类型只能查询和存储字符串。转换为StringBuffer类型后,进入for循环逐一查询字符串里面的字符,如果有重复的,则利用deletecharAt()方法将后面的重复字符删去。通过这题,我学习了String和StringBuffer及其内含方法的使用。并且,不能直接对StringBuffer进行赋值,但是由于StringBuffer类型比String类型更加方便,所以,在对字符串内容进行增删改的时候将其转换为StringBuffer类型更好。
集训一7-8从一个字符串中移除包含在另一个字符串中的字符
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); String s1 = aaa.next(); String s2 = aaa.next(); StringBuffer ss1 = new StringBuffer(s1); int i,j; for(i=0;i<s2.length();i++) for(j=0;j<ss1.length();j++){ if(s2.charAt(i)==ss1.charAt(j)){ ss1.deleteCharAt(j); j--; } } System.out.print(ss1); } }
首先输入两个字符串存放在s1,s2中,其中,s1为被分析的字符串,s2为样本字符串。所以,应该将字符串s1转换为StringBuffer类型方便后续删去字符串。由于样本字符串一般更短,所以,将样本字符串作为外循环的长度,被分析字符串的长度作为内循环的长度。如果出现s1的字符与s2的字符一样,就删去这个字符同时内循环用于计数的j的值减去1。这是因为字符串s1删去了一个字符,其长度必然减少1,如果j的值不进行减1的话,将遗漏重复字符后面的一个字符造成错误。但是,经过此次分析,大概明白为什么有一个测试点不让过的原因了。因为假如字符串s1中包含了s2的全部字符,但是这些字符并不是连续的,也就是说,s1中并不包含s2字符串,在这样的算法下,也会删去s1中与s2重复的单个字符,这样就不符合题意了。所以,通过这个题目,让我明白写程序之前应该尽量详尽地思考好算法再动手写,这样就能够避免很多逻辑错误。
集训一7-12列出最简真分数
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner aaa = new Scanner(System.in); int denomination = aaa.nextInt(); int i = 1,j = 1,k = denomination,m = 0; for(i = 1;i<denomination;i++){ m = i; k = denomination; while((j = k % m)!=0){ k = m; m = j; } if(m!=1) continue; else System.out.print(i + "/"+denomination + ","); } } }
首先输入一个正整数——分母,令该数等于K,是为了让分母参与部分运算但是不改变分母的值。进入循环,让i的值依次加一,判断k与i取余是否为0;不为0则用m除以余数j,循环往复,就是辗转相除法。如果最终得到的分子不是1,则输出这个分数。这是由于当i等于1的时候,1/denomination已经输出过了,应该要避免再次重复输出。通过这个题目,我收获最大的是学会了利用辗转相除法求俩个数的最大公约数。其次,锻炼了我的问题分析能力和代码改错能力,让我受益匪浅。
集训二7-2求奇数和
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); int[] a = new int[10]; int i,sum = 0; for(i=0;i<10;i++) a[i] = aaa.nextInt(); for(i=0;i<10;i++) if(a[i]%2!=0) sum += a[i]; System.out.print(sum); } }
首先,定义一个数组a,通过for循环对其进行赋值,然后用for循环遍历数组每一个元素,用if语句让每个数组元素对2取余,得到的结果不为0则加起来,最后输出sum。通过这题,让我学习了Java中数组的定义及其赋值和遍历,与c语言中数组的定义及遍历有很多共同点,唯一不同的是,Java中数组定义之后需要自己用new方法给数组分配内存空间,而C语言可以自行给数组分配空间。相同的是,Java中的数组和C语言的一样无法对整个数组进行操作,都需要借助循环实现数组赋值及数组元素的遍历。
集训二7-3房产税费计算
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); int a = aaa.nextInt(); int b = aaa.nextInt(); int c = aaa.nextInt(); double d = aaa.nextDouble(); double x,y,m,n; if(a==1){ if(d<=90) x = 0.01*c*10000; else if(d>90&&d<=144) x = 0.015*c*10000; else x = 0.03*c*10000; } else x = 0.03*c*10000; y = 0.0005*b*10000; m = 3.0*d; n = 1.36*d; System.out.print((float)x + " " + (float)y + " " + (float)m + " " + (float)n); } }
首先,定义三个整型类型的数据并输入,定义一个double类型数据并输入。先对a进行判断,如果a等于1 ,即是第一次买房,则进入房款的判断,小于90,大于90小于144,大于144,等多次判断,每一个执行不一样的操作。最后输出各种费用。这里涉及到double类型比float类型的数据存放更加精确的问题.。若用float型存放并计算某些数据,可能产生数据精确性丢失的问题。而在输出时有要求不需要这么精确,导致在类型转换之前,都是有好多测试点过不去。其中,7-3,7-4,7-6都出现过这样的问题。其次,我发现此题的算法还尚有斟酌的地方,比如,第一次购房但是房款在144万元以上和非第一次购房的契税算法是一样的,但是我这样的写法却重复了一段代码,代码应该还有精简的空间。这有一次提醒我在写题目的时候不要急于动手,应该先想清楚精简且正确的算法再操作。
集训三7-1创建圆形类
import java.util.Scanner; public class Main{ static Scanner aaa = new Scanner(System.in); private static double radius = aaa.nextDouble(); public static void main(String[] args){ try { if(Radiusright(radius)==1){ System.out.printf("The circle's radius is:%.2f\n",Getter()); System.out.printf("The circle's area is:%.2f",Area(radius)); } else System.out.print("Wrong Format"); } catch (Exception e) { e.printStackTrace(); } } public static double Getter(){ return radius; } public static int Radiusright(double radius){ if(radius<0) return 0; else return 1; } public static double Area(double radius) return Math.PI*radius*radius; } }

首先定义一个私有类型radius,在Main方法中输入radius然后调用RadiusRight方法判断输入的radius是否合法,合法则通过调用getter和Area方法获取radius与area并输出。本题让我学习了私有类型数据的获取方法和方法之间相互调用的技术,让我对模块化程序设计有了更深的了解。但是由于对类的使用方法缺乏足够的学习,本题并没有将这些方法组合在一个circle类里面。会发现其实将属性和方法放在一起,起一个名字circle就可组成一个类,但当时的我并没有进行深入的学习,所以并不知道。经过此题的训练,我明白了,在接触到一个新的知识点 的时候,应该先进行系统的学习之后,通过写题来巩固这个知识点才是正确的学习方法。
集训三7-2创建账户类Account
import java.time.LocalDate; import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); Account account1 = new Account(); account1.setid(aaa.nextInt()); account1.setbalance(aaa.nextDouble()); account1.setannuallnterestRate(aaa.nextDouble()); double ba1 = aaa.nextDouble(); double ba2 = aaa.nextDouble(); double ba3 = account1.getbalance(); if(ba1>ba3||ba1<0){ System.out.println("WithDraw Amount Wrong"); } else account1.withDraw(ba1); if(ba2>20000||ba2<0){ System.out.println("Deposit Amount Wrong"); System.out.printf("The Account'balance:%.2f\n",account1.getbalance()); } else System.out.printf("The Account'balance:%.2f\n",account1.getdeposit(ba2)); System.out.printf("The Monthly interest:%.2f\n",account1.getMonthlyInterestRate()); System.out.println("The Account'dateCreated:" + account1.getdateCreated()); } } class Account{ private int id = 0; private double balance = 0; private double annuallnterestRate = 0; private LocalDate dateCreated = LocalDate.of(2020,7,31); public void setid(int id){ this.id = id; } public void setbalance(double balance){ this.balance = balance; } public void setannuallnterestRate(double annuallnterestRate){ this.annuallnterestRate = annuallnterestRate; } public double getbalance(){ return this.balance; } public double getannuallnterestRate(){ return this.annuallnterestRate; } public LocalDate getdateCreated(){ return this.dateCreated; } public void withDraw(double ba1){ this.balance = this.balance-ba1; } public double getdeposit(double ba2){ this.balance = this.balance+ba2; return this.balance; } public double getMonthlyInterestRate(){ return this.balance*(getannuallnterestRate()/1200); } }

创建一个Account类,里面有私有型数据,id,balance,annuallnterestRate,dateCreate,再写三个setter方法对前三个数据进行改变数值,然后写四个getter方法,以便在主方法里面调取私有属性的值。在主方法中完成对相应数字的合法性的判断和调用Account类里面的方法完成计算。通过这次的题目,我掌握了构建简单类的方法,和对私有属性的附上初值之后对其进行改变的方法。和私有属性在主方法中如何输出。通过此题,我也了解了一个新的参数类型——localdate,用这个属性来输出日期很方便,这也是Java与C语言的不同之处。通过此题,我也了解了如何在主类的主方法里面调用自定义的类里面的方法。就像是C语言里面的结构体里面的参数调用一样,需要指出该结构体的名字和里面实际存在的数据名字才能用。而Java里面的类的调用就是先调用这个类,然后再写上类里面的相应方法,若方法需要形参传递过去,还应在括号里面将主类主方法里面的参数传递过去,否则就会报错。
集训三7-3求下一天
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner aaa = new Scanner(System.in); Date date1 = new Date(); date1.setyear(aaa.nextInt()); date1.setmonth(aaa.nextInt()); date1.setday(aaa.nextInt()); if(date1.checkInputValidity()){ date1.setNextDate(); System.out.println("Next day is:"+date1.getyear()+"-"+date1.getmonth()+"-"+date1.getday()); } else System.out.print("Date Format is Wrong"); } } class Date{ private int year; private int month; private int day; int[] m = {0,31,28,31,30,31,30,31,31,30,31,30,31}; public void setyear(int year){ this.year = year; } public void setmonth(int month){ this.month = month; } public void setday(int day){ this.day = day; } public int getyear(){ return year; } public int getmonth(){ return month; } public int getday(){ return day; } public boolean isLeapYear(){ if(this.year%400==0||(this.year%4==0&&this.year%100!=0)) return true; else return false; } public boolean checkInputValidity(){ if(isLeapYear()) m[2] += 1; if((year>=1900&&year<=2000)&&(month>=1&&month<=12)&&(day>=1&&day<=m[month])) return true; else return false; } public void setNextDate(){ if(this.month<12){ if(this.day<m[this.month]) this.day = this.day+1; else{ this.day = 1; this.month = this.month+1; } } else{ if(this.day<m[this.month]) this.day = this.day+1; else{ this.day = 1; this.month = 1; this.year = this.year+1; } } } }

先定义一个Date类,里面有私有属性:year,month,day,再加一个数组m,初始化时,将数组里面的元素依次安排为每个月的日期数(其中,二月先赋值28,若判定为闰年的year则对m[2]单独加1)。在主类主方法中,先给Date类分配内存空间date1,然后调用date1中的三个 setter方法,输入year,month,day同时将三个数传入Date类里面,然后再调用Date类里面的Nextday方法,计算出nextday并输出下一天。主要在Date类里面,完成日期输入合法性和求下一天的操作。在主方法中,日期输入之后,先调用checkInputValidity方法判断日期是否合法,若不合法则输出特定的语句,若合法,则回到Date类里面,先判断是否是闰年,若是,则在方法内将m[2]的值加一,若不是,则返回false。然后到nextday里面计算下一天,根据每个月的天数不同对其进行加一天操作。通过这次题目,我能更加熟练地运用构建格外 的类来处理复杂的问题使主类主方法变得更加精简。同时,也让我更加熟练地对类里面的方法进行引用从而解决问题。本次题目最大的收获是运用数组保存每个月的天数,使涉及到日期计算的题目能更加精简的解决,提高了代码的质量,不像之前那样写一大段无效代码,优化了算法。
集训三7-4日期类的设计
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner input = new Scanner(System.in); int year = 0; int month = 0; int day = 0; int choice = input.nextInt(); if (choice == 1) { // test getNextNDays method int m = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } m = input.nextInt(); if (m < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:"); System.out.println(date.getNextNDays(m).showDate()); } else if (choice == 2) { // test getPreviousNDays method int n = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } n = input.nextInt(); if (n < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print( date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:"); System.out.println(date.getPreviousNDays(n).showDate()); } else if (choice == 3) { //test getDaysofDates method year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); int anotherYear = Integer.parseInt(input.next()); int anotherMonth = Integer.parseInt(input.next()); int anotherDay = Integer.parseInt(input.next()); DateUtil fromDate = new DateUtil(year, month, day); DateUtil toDate = new DateUtil(anotherYear, anotherMonth, anotherDay); if (fromDate.checkInputValidity() && toDate.checkInputValidity()) { System.out.println("The days between " + fromDate.showDate() + " and " + toDate.showDate() + " are:" + fromDate.getDaysofDates(toDate)); } else { System.out.println("Wrong Format"); System.exit(0); } } else{ System.out.println("Wrong Format"); System.exit(0); } } } class DateUtil{ private int year; private int month; private int day; int[] m = {0,31,28,31,30,31,30,31,31,30,31,30,31}; public DateUtil(int year,int month,int day){ this.year = year; this.month = month; this.day = day; } public int getYear(){ return this.year; } public int getMonth(){ return this.month; } public int getDay(){ return this.day; } public boolean isLeapYear(int year){ if(year%400==0||(year%4==0&&year%100!=0)) return true; else return false; } public boolean checkInputValidity(){ if(isLeapYear(year)) m[2] += 1; if(year>=1820&&year<=2020&&month>=1&&month<=12&&day>=1&&day<=m[month]) return true; else return false; } public DateUtil getNextNDays(int n){ int a,b=0,c,i,j; if(n>365){ a = n/365; c = a/4+1; this.year += a; n %= 365; n -= c; } if(n>m[month]-day) { for(i=0;i<month;i++) b += m[i]; b += day; if(n<365-b) { for(i=month+1,j=1;i<12;i++,j++) { n -= m[i]; if((n-day)<m[i+1]) break; } this.month += j; this.day = n-day; } else { n -=(365-b); for(i=0;i<month;i++) { n -= m[i]; if(n<m[i+1]) break; } this.year += 1; this.month = i+1; this.day = n; } } else this.day += n; return this; } public DateUtil getPreviousNDays(int n){ int a,b=0,c,i,j; if(n>365){ a = n/365; this.year -= a; n %= 365; } else{ if(n>day) { for(i=month-1;i>0;i--) b += m[i]; b += day; if(n<b) { for(i=month-1;i>0;i--) { n -= m[i]; if(n<m[i]) break; } this.month = i; this.day = n; } else { n -=(365-b); for(i=0;i<month;i++) { n -= m[i]; if(n<m[i+1]) break; } this.year += 1; this.month = i; this.day = n; } } else this.day -= n; } return this; } public boolean compareDates(DateUtil date){ if(this.year<year||(this.year==year&&this.month<month)||(this.year==year&&this.month==month&&this.day<day)) return true; else return false; } public boolean equalTwoDates(DateUtil date){ if(this.year==year&&this.month==month&&this.day==day) return true; else return false; } public int getDaysofDates(DateUtil date){ int a,b,c,i,sum=0; if(compareDates(date)) { a = year-this.year; b = month-this.month; c = day-this.day; } else { a = this.year-year; b = this.month-month; c = this.day-day; } for(i=0;i<a;i++) { if(isLeapYear(year+i)) sum += 366; else sum += 365; } for(i=0;i<b;i++) { sum += m[month+i]; } sum += m[month]-this.day+day; return sum; } public String showDate(){ return year+"-"+month+"-"+day; } }
此题看似和集训三7-3的算法相近,但是对于基础不好有是初学者的我来说,难度上了一个档次。求下n天,如果这个n天的范围扩大到了365以上,就需要思考很多问题了,比如,今年是否是闰年关系到往下的四年日期计算,往前的4年日期计算,还有,求两天相差的天数还涉及到两个日期之间究竟有几个闰年,需要进行相应的推算。解决完闰年日期的问题,还有两日期之间间隔少于365天,有涉及月份天数的计算,虽然都是加减法,但是真的很难。同7-3的思路相近,先设计一个Date类尽量将各种数据操作的方法放到了里面,尽量使主类主方法的操作更加精简。输入年月日,先判断年月日是否合法,同7-3的思路,都是将每个月的天数放在一个数组m里面,先查询此年是否为闰年确定二月的天数,然后再将年月日导入check方法判断。再将其放入nextday方法里面,先判断n是否大于365,若大于则先除以365初步确定所求日期距离输入的日期的年份,再将n与365取余得到的数往前推算,最后得出结论,计算前n天也是同理。而计算两日期相距天数先计算相隔几年,然后计算中间夹了多少个闰年,多少个闰年就再多加几个1,最后,计算前一个日期距离下一年所距离的天数,加上下一个日期距离该年的日期数。然后再加上中间夹的整年数。经过此次题目,我明白了写程序之前的算法一定要想清楚,不然真的越写越乱。
踩坑心得
1、强制类型转换:在集训二当中,有好多题涉及到double型数据参与运算,float型数据输出的坑。原因是double型数据比float型的更精确,参与运算能让数据更加精准,不至于导致精度的丢失,但是在输出结果的时候,往往不需要那么精确,保留那么多位小数,所以,应该在输出时进行强制类型转换保证精度同时又能使输出精简。(虽然这个理由比骄正当,但是对于一个输出问题花费了几个小时才整明白是真的不太厚道)
2、输出格式的问题:在PTA中对输出格式有严格的规定,比如,保留两位小数,转化为字符型输出。特别是保留两位小数,在查阅了大量资料,辗转多个网站和翻阅课本多次才弄明白,原来可以像C语言中的printf函数一样输出,只不过在Java中前面要加上System.out。同时,Java中,输出一个数据只需要加号就可以而初学时总是习惯用%d之类的。想起来一句话,什么都习惯,只会害了你。放在这里也是非常合适的哈哈。
3、类间关系的问题,初学的时候,总是不知道如何调用类里面的方法,后来参考了课本上的例子,再结合C语言中结构体的数据的运用才解决这个问题。
改进建议:
1、建议老师对运算需要高精度的,但是结果不要求高精度的题再题目里面先说出来,以减少我们因为输出的问题而浪费大量的时间
2、建议老师将多个知识点柔和在一起出题比较好,对我们的能力会有更好的提升
总结:
通过这三次集训,我学会了编辑Java程序的基本语法,和数据的输入输出等操作,学会了数组的定义及其初始化和利用循环对数组元素进行赋值和遍历,对比C语言中的数组操作,最大的不同就是,Java中的数组在定义之后,应该为其分配相应的内存空间,不像C语言,会自动给数组分配内存空间。同时我还还学会了字符串的操作及其遍历,学会了String,StringBuffer方法的不同之处。通过这次集训,我深刻的了解了我编程能力的欠缺和语法的学习非常重要。在后续的学习中,我将更注重自己编程能力的训练和提高,提高算法的构思能力!

浙公网安备 33010602011771号