第一次部落格:PTA題目集1-3總結

發表於2023-04-01
             第一次部落格: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-37-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 有重複的資料:使用雙迴圈明顯是不明智的,這裡建議使用sortMAP判重 因為只需要判斷是否重複不需要將原輸入進行處理後輸出所以無論是使用那一種都十分的方便。下面是我使用的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呢???因為題目要求按原來的資料進行輸出而SetHashSet記憶體放的資料都是無序的(與輸入順序不同)而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.每次作業截止之後可以出一下不計分的補題,可以讓沒有及時解決問題的同學繼續嘗試。