考试题目
第一题(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);
}