考試題目
第一題(10分)
需求
目前有100名囚犯,每個囚犯的編號是1-200之間的隨機數。現在要求依次隨機生成100名囚犯的編號(要求這些囚犯的編號是不能重復的),然后讓他們依次站成一排。(注:位置是從1開始計數的),接下來,國王命令手下先干掉全部奇數位置處的人。剩下的人,又從新按位置1開始,再次干掉全部奇數位置處的人,依此類推,直到最后剩下一個人為止,剩下的這個人為幸存者。
具體功能點的要求如下:
請輸出幸存者的編號,以及他第一次所占的位置值是多少。
評分細則
能做出第一步:生產100個隨機編號,且占位成功的,給3分。
能成功刪除奇數位置處的數據的,給5分。
能正確獲取結果的給2分。
應該能得10分?但是沒有用答案的對象做,而是直接使用數組實現的
集合也做出來了,反正是一題多解的啦
?數據模板:
public class People {private int index; //位置private int num; //編號public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}public int getNum() {return num;}public void setNum(int num) {this.num = num;}
}
集合方法:
public class test1 {public static void main(String[] args) {Random r = new Random();List<People> Prisoner = new ArrayList<>();//生成100個囚犯for(int i=0;i<100;i++){People p = new People();p.setIndex(i+1);p.setNum(createNum(Prisoner));Prisoner.add(p);}//打印初始100個囚犯的編號看下//for(int i=0;i<Prisoner.size();i++) System.out.print(Prisoner.get(i).getNum()+" ");//開始處刑while(Prisoner.size()>1){for(int i=1;i<=Prisoner.size();i++){if(i%2!=0){ //奇數處刑Prisoner.get(i-1).setIndex(0); //將位置設置為0,代表處刑了}}for(int i=0;i<Prisoner.size();i++){if(Prisoner.get(i).getIndex()==0){Prisoner.remove(i);i--;}}}System.out.println("幸存者編號:"+Prisoner.get(0).getNum()+",幸存者第一次所在位置:"+Prisoner.get(0).getIndex());}//生成不會重復的編號,1-200之間public static int createNum(List<People> Prisoner){Random r = new Random();int num = 0;while (true) {int flag=1;num = r.nextInt(200)+1;for(int i=0;i<Prisoner.size();i++){if(num==Prisoner.get(i).getNum()){flag = 0;break;}}if (flag==1) break;}return num;}
}
?數組方法:
public class test {public static void main(String[] args) {int[] prisoner = createPrisoner();int[] prisoner1 = new int[prisoner.length];for(int i=0;i<prisoner.length;i++) prisoner1[i] = prisoner[i];int length = prisoner.length; //length=101System.out.println(Arrays.toString(prisoner));while(length>2){ //當數組中只剩 0 1時,就只剩一人了,0不站人int count = 0;//記錄處刑了幾個人for(int i=1;i<length;i++){if(i%2!=0){ //奇數prisoner[i]=0; //處刑了一個人count++;}}System.out.println("處刑完人:"+Arrays.toString(prisoner));int[] temp = new int[length-count];for(int i=1,j=1;i<length;i++){if(prisoner[i]!=0){temp[j] = prisoner[i];j++;}}for(int i=1;i<length;i++) prisoner[i] = 0;for(int i=1;i<temp.length;i++) prisoner[i]=temp[i];System.out.println("重新排隊"+Arrays.toString(prisoner));length=length-count;}int num = prisoner[1];//幸存者編號int index = 0;//幸存者第一次站的位置for(int i=1;i< prisoner1.length;i++){if(num==prisoner1[i]){index = i;break;}}System.out.println(Arrays.toString(prisoner1));System.out.println("幸存者編號:"+num+",第一次所站的位置:"+index);}//隨機生成編號不重復的100個隨機的囚犯放入數組中,數組中存囚犯的編號public static int[] createPrisoner(){Random r = new Random();int[] prisoner = new int[101];//0-100,0不站人,length=101//prisoner[0] = -1; //數組0不站人for(int i=1;i<=100;i++){int x = r.nextInt(200)+1;int flag=1;for(int j=1;j<i;j++){if(x==prisoner[j]){flag=0;break;}}if(flag==1) prisoner[i]=x;else i--;}return prisoner;}}
第二題(14)
User 實體類,包含如下屬性
private Long id; // 用戶id 名
private String gender; //性別
private LocalDate birthday; //生日
注意需要提供 set和get方法,以及toString方法
新建測試類,類中 main 方法,在方法中完成如下業務邏輯:
業務一:
有如下字符串,里面包含多個用戶信息數據,現在需要你解析這個字符串,獲取里面的用戶數據,并封裝到User對象中
多個User對象在添加到List<User> 集合中
String userStrs = "10001:張三:男:1990-01-01#10002:李四:女:1989-01-09#10003:王五:男:1999-09-09#10004:劉備:男:1899-01-01#10005:孫悟空:男:1900-01-01#10006:張三:女:1999-01-01#10007:劉備:女:1999-01-01#10008:張三:女:2003-07-01#10009:豬八戒:男:1900-01-01";
注意:
字符串中的規則如下,多個用戶用 # 拼接,用戶的信息之間用 : 拼接。
其中用戶id和生日是需要進行類型轉換的,其中id需要將String轉成Long,生日需要將String轉成LocalDate
業務二:
遍歷上面獲取的List<User> 集合,統計里面每個名字出現的次數。
封裝到Map<String,Integer>集合中,集合的key就是名字,value就是名字出現的次數。
最后遍歷打印map數據,打印內容如下:
張三:3次
李四:5次
做是做出來了,但是剛開始使用方法比較麻煩,沒有想到用split
主要代碼:
public class test {public static void main(String[] args) {String userStrs = "10001:張三:男:1990-01-01#10002:李四:女:1989-01-09#10003:王五:男:1999-09-09#10004:劉備:男:1899-01-01#10005:孫悟空:男:1900-01-01#10006:張三:女:1999-01-01#10007:劉備:女:1999-01-01#10008:張三:女:2003-07-01#10009:豬八戒:男:1900-01-01";List<User> users = new ArrayList<>();String[] userStrArray = userStrs.split("#");System.out.println(Arrays.toString(userStrArray));for(String userdata : userStrArray){ //user會一個個取userStrArray中的值User user = new User();//根據 : 分割String[] userData = userdata.split(":");user.setId( Long.parseLong(userData[0]) );user.setName(userData[1]);user.setGender(userData[2]);user.setBirthday(LocalDate.parse(userData[3]));users.add(user);}for(int i=0;i<users.size();i++) System.out.println(users.get(i));Map<String,Integer> user_count = new HashMap<>();for(int i=0;i<users.size();i++){String name = users.get(i).getName();if(user_count.containsKey(name)){user_count.put(name, user_count.get(name)+1);}else{user_count.put(name, 1);}}user_count.forEach( (k,v) -> System.out.println(k+":"+v));}
}
數據模板:
public class User {private Long id; // 用戶idprivate String name;// 名private String gender; //性別private LocalDate birthday; //生日@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", gender='" + gender + '\'' +", birthday=" + birthday +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public LocalDate getBirthday() {return birthday;}public void setBirthday(LocalDate birthday) {this.birthday = birthday;}
}
第三題(16)
需求:
某護士小花,作息規律為 上二天班,休息一天,經常不確定休息日是否是周末
(注:首次休息日是2022年2月3日)。
具體功能點的要求如下
1、請你開發一個程序,當小花輸入年以及月后,立即顯示出該月份的休息日詳情。
示范(注意:示范信息重點在于參考格式,結果不一定是準確的,請自行確保計算結果正確性):
請小花輸入查詢的月份(月份必須是2022年2月之后的月份): 2023-5 。
2023-5-1[休息] 2023-5-2 2023-5-3 2023-5-4[休息] ...
2、顯示出該月份哪些休息日是周六或周日(請依次列出具體的日期和其星期信息)。
前兩題成功實現,使用map實現的,與視頻不太一樣,但是結果正確
public class Test {public static void main(String[] args) {Scanner sc = new Scanner(System.in);Map<LocalDate,Integer> day = new LinkedHashMap<>(); //1代表上班,0表示休息while (true) {System.out.print("請小花輸入查詢的月份(月份必須是2022年2月之后的月份):");String input_date = sc.next();String[] localdate = input_date.split("-");if(Integer.parseInt(localdate[0])<2022 || (Integer.parseInt(localdate[0])==2022 && Integer.parseInt(localdate[1])<=2 ) ){System.out.print("請輸入2022年2月之后的月份!");}else{if(Integer.parseInt(localdate[1])<10) localdate[1] = "0"+localdate[1];input_date = localdate[0] + "-" + localdate[1] + "-01";LocalDate in_date = LocalDate.parse(input_date);LocalDate first_rest = LocalDate.of(2022,2,3);//第一次休息day.put(first_rest,0);int flag=1; //當flag%3==0時,設置值為0表示要休息//如果不存在該月份,就一直添加到該月份,相當于集合中一定有輸入月份的下個月的一號 比如查5月,一定已經添加到6.1號了while( !(day.containsKey(in_date.plusMonths(1))) ){if(flag%3!=0){day.put(first_rest.plusDays(flag++),1);}else{day.put(first_rest.plusDays(flag++),0);}}day.forEach( (k,v) -> {if((k.isAfter(in_date) && k.isBefore(in_date.plusMonths(1))) || k.isEqual(in_date)){if(v==0){ //休息if(k.getDayOfWeek().getValue()==6){System.out.println(k+"[星期六休息]");}else if(k.getDayOfWeek().getValue()==7){System.out.println(k+"[星期天休息]");}else{System.out.println(k+"[工作日休息]");}}else{System.out.println(k);}}});}}}
}
3、小花給自己設置了一個高考倒計時。高考的開始時間為:2023年06月07日 上午9:00 。
**請利用給的素材代碼(在Timer文件夾下)**,補全代碼,產生一個如下的倒計時效果,倒計時格式如下圖所示:
Timer文件夾代碼:
public class TimeTask extends TimerTask {// 高考開始時間private LocalDateTime startTime;// 構造器,對高考的開始時間進行初始化public TimeTask() {String s = "2023-06-07 09:00:00";// 補全代碼}// 每一秒執行一次該方法@Overridepublic void run() {// 補全代碼:完成倒計時效果}}
public class Start {public static void main(String[] args) {// 創建一個定時器對象Timer timer = new Timer() ;timer.schedule(new TimeTask(), 0 , 1000); // 每隔1秒執行一次new TimeTask()里的run方法}
}
這題完成的應該沒啥問題,結果也和網上的倒計時一樣,但是沒想到居然有那么簡單的方法,可以直接在后面加part就能計算出來了? ? ? duration.toHoursPart()?
duration.toHours()? ? 而我全程用這個,如下圖
最終代碼:
public class TimeTask extends TimerTask {// 高考開始時間private LocalDateTime startTime;// 構造器,對高考的開始時間進行初始化public TimeTask() {String s = "2026-06-07 09:00:00"; //高考時間// 補全代碼DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");startTime = LocalDateTime.parse(s,dateTimeFormatter);LocalDate startDate = startTime.toLocalDate();//System.out.println(startTime);System.out.println("高考倒計時");System.out.println(startTime.getYear()+"年高考時間:"+startDate+" "+startTime.getDayOfWeek());System.out.println("現在距離高考還有");}// 每一秒執行一次該方法@Overridepublic void run() {// 補全代碼:完成倒計時效果LocalDateTime nowtime = LocalDateTime.now();Duration duration = Duration.between(nowtime,startTime);System.out.println(duration.toDays()+" 天 "+duration.toHoursPart()+" 小時 "+duration.toMinutesPart()+" 分鐘 "+duration.toSecondsPart()+" 秒 ");}
}
public class Start {public static void main(String[] args) {// 創建一個定時器對象Timer timer = new Timer() ;TimeTask timeTask = new TimeTask();timer.schedule(timeTask, 0 , 1000); // 每隔1秒執行一次new TimeTask()里的run方法}
}
第四題(22分)
需求:
ArrayList集合是很重要的一種集合,請手工書寫一個MyArrayList集合模擬ArrayList集合。
具體功能點的要求如下:
1、MyArrayList需要支持泛型,內部使用數組作為容器。
2、在MyArrayList中開發add方法,用于添加數據的,需要遵循ArrayList的擴容機制(自行設計代碼,不需要與ArrayList的源代碼一樣,思想一致即可)
3、在MyArrayList中開發根據索引查詢數據的get方法。
4、在MyArrayList中開發根據索引刪除數據的remove方法。
5、在MyArrayList中開發一個獲取集合大小的size ()方法。
6、能夠在MyArrayList集合中開發一個forEach方法,這個方法支持使用Lambda進行遍歷,至于函數式接口叫什么名稱無所謂。
7、編寫測試用例對自己編寫的MyArrayList集合進行功能正確性測試。
forEach不會,其他都做出來了
不過還能優化一下:比如把擴容功能獨立出來、搞一個越界異常、remove數據時采用移動數據的方式而不是新建數組
鞏固知識:
函數式接口就是解決函數不能作為參數傳入另一個函數的問題,將想傳的函數包裝成只有一個方法的類(或者接口),就可以用lambda表達式了
主要代碼:
public class MyArrayList<E> {private Object[] mylist = new Object[0]; //存滿時擴容1.5倍private int size = 0; //代表集合中數據數量,也代表下一個要存入數據的位置public boolean add(E e){if(size == mylist.length) expand();mylist[size++] = e;return true;}public E get(int index){checkIndex(index);return (E)mylist[index];}public E remove_create(int index){checkIndex(index);E e = null;Object[] temp = new Object[mylist.length];for(int i=0,j=0;j<mylist.length;i++,j++){if(j==index){e = (E)mylist[i];i--;continue;}temp[i] = mylist[j];}mylist = temp;return e;}public E remove_move(int index){checkIndex(index);E e = (E)mylist[index];for(int i = index;i<size-1;i++){mylist[i] = mylist[i+1];}mylist[--size] = null;return e;}public int size(){return size;}public void forEach(myConsumer<E> action){Objects.requireNonNull(action);//傳入的方法不能為空for(int i=0;i<size;i++){action.accept( (E)mylist[i] );}}//檢查越界異常public void checkIndex(int index){if(index>=size || index<0){throw new IndexOutOfBoundsException("越界異常!輸入索引 " + index + " 超過最大索引 " + (size - 1));}}//擴容public void expand(){if(size==0){mylist = new Object[10];}else{mylist = Arrays.copyOf(mylist,(int)(mylist.length * 1.5));}}@Overridepublic String toString() {StringBuffer sb = new StringBuffer();sb.append("["+mylist[0]);for(int i=1;i<size;i++){sb.append(", "+mylist[i]);}sb.append("]");return sb.toString();}
}
函數式接口:
@FunctionalInterface
public interface myConsumer<E> {void accept(E e);//接收數據
}
第五題(16分)
需求:
給你一個按照非遞減順序排列的整數數組 nums
,和一個目標值 target
。請你找出給定目標值在數組中的開始位置和結束位置。
如果數組中不存在目標值 target
,返回 [-1, -1]
。
注意:必須確保程序的時間復雜度是o(log2n),否則不給分數
具體功能點的要求如下:
數組 nums = [5,7,7,8,8,10], target = 8 得到結果是:[3,4]
數組:nums = [5,7,7,8,8,10], target = 6 得到結果是:[-1,-1]
數組:nums = [], target = 0 得到結果是:[-1,-1]
請設計一個方法完成以上需求,并編寫測試代碼完成上述測試。
沒做對,最壞時間復雜度為o(n),應該嚴格使用二分查找找到左邊的數與右邊的數,而我采用的方法是用二分查找找到中間的數后依次往左和往右遍歷,如果數組中所有的數都是一樣的話,時間復雜度就是o(n)
我的做法:
public static void main(String[] args) {int[] num = {1,2,3,3,3,3,4,5,6};int target = 3;int index = Arrays.binarySearch(num,target);int start = -1, end = -1;if (index>=0) {for(int i=index; ;i--){if(num[i]!=target){start = i+1;break;}}for(int i=index; ;i++){if(num[i]!=target){end = i-1;break;}}}System.out.println("["+start+", "+end+"]");}
?正確做法:
public class test {public static void main(String[] args) {int[] num = {5, 7, 7, 7, 7, 8, 8, 9};int target = 7;int start = searchStart(num,target);int end = searchEnd(num,target);System.out.println("["+start+", "+end+"]");}public static int searchStart(int[] num,int target){int low = 0;int high= Arrays.binarySearch(num,target);if(high<0) return -1;int rs = -1;while(low<=high){int mid = (low+high)/2;if(num[mid] < target){low = mid + 1;}else if(num[mid] > target){high = mid - 1;}else{high = mid - 1;rs=mid;}}return rs;}public static int searchEnd(int[] num, int target){int low = Arrays.binarySearch(num,target);int high = num.length-1;if(low<0) return -1;int rs = -1;while(low<=high){int mid = (low+high)/2;if(num[mid] < target){low = mid + 1;}else if(num[mid] > target){high = mid - 1;}else{low = mid + 1;rs = mid;}}return rs;}
}
第六題(22)
需求
給你單鏈表的頭指針 head 和兩個整數 left 和 right ,其中 left <= right 。請你反轉從位置 left 到位置 right 的鏈表節點,并返回 反轉后的鏈表 。
示例 1:
比如 head 指向的鏈表內容大致是 1,2,3,4,5 , left = 2, right = 4 ,反轉后的鏈表就是 1,4,3,2,5
如果鏈表只有一個節點:head指向的是 5 ,left = 1, right = 1 反轉后的鏈表就還是 5
具體功能點的要求如下
1、設計一個Node泛型類,用于代表鏈表的結點。每個結點包含(數據data,和下一個結點的地址值next) 3
2、開發一個類叫MyLinkedList,提供一個add方法可以讓用戶添加鏈表的結點,直到用戶輸入exit,則返回鏈表(返回鏈表實際上是返回鏈表的頭結點) 5
3、提供一個reverse方法,接收頭指針 head 和兩個整數 left 和 right ,其中 left <= right,按上面的要求進行反轉。反轉后,返回新的鏈表 9
4、提供一個forEach方法,接收新鏈表,并對其進行遍歷輸出。 5
剛開始有點忘記鏈表怎么創建了,反正琢磨琢磨、復習了一下鏈表,才寫出第一個add,然后獨立寫出了reverse和foreach,foreach實現了兩種寫法
但是這里的第一題要求實現泛型類有點不好實現,我看視頻也沒有實現泛型類,因為如果要輸入的話輸入數據的類型是String,沒法通過強轉將它轉換成Integer或別的類,ai了一下感覺有點難度,還用到了Function< , >,先放一下吧,實現效果圖如下
主函數:
public class test {public static void main(String[] args) {//1 2 3 4 5 6 7 8 9 exitMyLinkedList<String> myLinkedList = new MyLinkedList();myLinkedList.add();System.out.println("第一次添加:");//模擬forEachmyLinkedList.forEach( s-> System.out.print(s+" ") );System.out.println();myLinkedList.add();System.out.println("再次添加:");//循環打印forEach(myLinkedList);int left = 3,right = 7;MyLinkedList<String> reverseList = myLinkedList.reverse(myLinkedList.head,left,right);System.out.println(left+"-"+right+"翻轉:");forEach(reverseList);}public static void forEach(MyLinkedList list){Node node = list.head;while(node!=null){System.out.print(node.getData()+" ");node = node.getNext();}System.out.println();}
}
鏈表:
public class MyLinkedList<E> {int size; //記錄鏈表的大小Node<E> head; //鏈表的頭結點Node<E> tail;//鏈表的尾指針,用于尾插法Function<String,E> converter;Scanner sc = new Scanner(System.in);public Node<E> add(){System.out.println("請持續輸入要添加的數據(輸入exit停止輸入):");String data = sc.next();while (!(data.equals("exit"))){if(head==null){ //還不存在結點head = new Node<>();head.setData((E)data);head.setNext(null);tail = head;size++;}else{ //已存在結點,利用尾插法插在末端Node<E> node = new Node<>((E)data,tail.getNext());tail.setNext(node);tail = node;size++;}data = sc.next();}return head;}//核心思路:left與right中間包夾的部分使用頭插法,其他部分使用尾插法//比如 1 2 3 4 5 6 7 8 9 ,left = 3,right = 7//構造一個帶頭結點的鏈表,在插入結點時,1-2與8-9使用尾插法保持有序,而3-7使用頭插法使其逆序public MyLinkedList<E> reverse(Node head,int left,int right){MyLinkedList<E> newLinkedList = new MyLinkedList<>();newLinkedList.head = new Node<>();//頭結點,且為空newLinkedList.tail = newLinkedList.head;//尾指針初始指向頭結點Node node = head;//用來遍歷原鏈表Node new_head = null;//用來充當頭插法中的頭指針for(int i=1;i<=size;i++){if(i<left || i>right){ //尾插法//尾插法是將新結點插入尾部,因此新插入結點的next一定是nullNode new_node = new Node(node.getData(),null);newLinkedList.tail.setNext(new_node);//始終保持尾指針指向最后一個結點newLinkedList.tail = new_node;}else{ //頭插法//開啟頭插法時,將鏈表的最后一個結點,即尾指針指向的結點視設置為頭插法中的頭結點//每次插入都在這個頭結點之后插入新結點if(i==left) new_head = newLinkedList.tail;//頭插法是將新結點插在頭結點之后,因此插入結點的next就是頭結點的next//比如 頭->1->null 插入2 2的next就是頭的next,頭的next是1,再將頭的next改為2//因此 頭->2->1->nullNode new_node = new Node(node.getData(),new_head.getNext());new_head.setNext(new_node);//因為頭插法相當于輸入是倒序,因此頭插法的第一個結點會成為最后一個,//此時將新鏈表的尾指針指向它,這樣等頭插法結束后尾指針仍然指向這個鏈表的最后一個結點if(i==left) newLinkedList.tail = new_node;}node = node.getNext();}//由于要返回一個鏈表,因此為了保持和原有鏈表的一致性,去除空的頭結點,將頭結點設置為有數據的一個結點newLinkedList.head = newLinkedList.head.getNext();return newLinkedList;}public void forEach(myConsumer<E> action){Objects.requireNonNull(action);Node<E> node = head;while(node!=null){action.accept( node.getData() );node = node.getNext();}}
}
結點:
public class Node<E> {private E data; //數據private Node<E> next; //下一個結點地址public Node() {}public Node(E data, Node<E> next) {this.data = data;this.next = next;}public E getData() {return data;}public void setData(E data) {this.data = data;}public Node<E> getNext() {return next;}public void setNext(Node<E> next) {this.next = next;}
}
函數式接口:
@FunctionalInterface
public interface myConsumer<E> {void accept(E e);
}