考试题目
第一题(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=101
System.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; // 用户id
private String name;// 名
private String gender; //性别
private LocalDate birthday; //生日
@Override
public 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";
// 补全代码
}
// 每一秒执行一次该方法
@Override
public 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("现在距离高考还有");
}
// 每一秒执行一次该方法
@Override
public 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));
}
}
@Override
public 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 exit
MyLinkedList<String> myLinkedList = new MyLinkedList();
myLinkedList.add();
System.out.println("第一次添加:");
//模拟forEach
myLinkedList.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一定是null
Node 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->null
Node 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);
}