Java 笔试强训 牛客网选择编程题 03

单选

下面选项中,满足短任务优先(执行任务时间最短的最先执行)且不会发生饥饿现象的调度算法是()。

  • A 先来先服务
  • B 高响应比优先
  • C 时间片轮转
  • D 非抢占式短任务优先

先来先服务 (ECES, First Come First Serve)

最简单的调度算法,按先后顺序进行调度。

高响应比优先(HRRN,Highest Response Ratio Next)

综合考虑作业/进程的 等待时间 和 要求服务的时间,在每次调度时先计算各个作业/进程的响应比,选择响应比最高的作业/进程为其服务。

高响应比优先调度算法是介于 ECES (先来先服务算法) 与 SE (短作业优先算法) 之间的折中算法,既考虑作业等待时间又考虑作业运行时间,既照顾短作业又不使长作业等待时间过长,改进了调度性能

响应比 = (等待时间 + 要求服务时间) / 要求服务时间

时间片轮转调度(Round-Robin,RR)

用于分时系统中的进程调度。每次调度时,总是选择就绪队列的队首进程,让其在CPU上运行一个系统预先设置好的时间片。一个时间片内没有完成运行的进程,返回到绪队列末尾重新排队,等待下一次调度。

短作业优先 (SJE, Shortest Job First)

SIE是非抢占式的算法。最短的作业/进程优先得到服务(所谓"最短”,是指要求服务时间最短)

系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成。容易造成饥饿。

下列选项中,降低进程优先级的合理 时机 是()

  • A 进程的时间片用完
  • B 进程刚完成I/O,进入就绪列队
  • C 进程持久处于就绪列队
  • D 进程从就绪状态转为运行态

在使用锁保证线程安全时,可能会出现活跃度失败的情况,活跃度失败主要包括

  • A 死锁
  • B 饥饿
  • C 活锁
  • D 以上全部

活跃度问题是指线程或进程长时间得不到 cpu占用。

A.死锁:线程间互相持有锁,并等待对方释放资源,结果谁也得不到执行。

B.饥饿:如每次都执行优先级高的线程,那么优先级低的可能永远执行不到。

C.活锁:活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活",而处于死锁的实体表现为等待; 活锁有可能自行解开,死锁则不能。

选择排队作业中等待时间最长的作业优先调度,该调度算法是()。

  • A 先来先服务调度算法
  • B 高响应比优先调度算法
  • C 优先权调度算法
  • D 短作业优先调度算法

高优先权优先调度算法

指紧迫型作业(优先级高的任务)进入系统后能得到优先处理。

对进程和线程的描述,以下正确的是()

  • A 父进程里的所有线程共享相同的地址空间,父进程的所有子进程共享相同的地址空间

  • B 改变进程里面主线程的状态会影响到其他线程的行为,改变父进程的状态不会影响到其他子进程

  • C 多线程会引起死锁,而多进程不会

  • D 以上选项都不正确

A.父进程和子进程都有自己独立的地址空间;

B.主线程和子线程是并行关系的时候,并没有依赖关系。改变父进程的状态,比如父进程退出的同时,通知子进程退出,那么就可以影响到子进程状态,如果没有任何通知,就不会影响到子进程。

C.如果多个进程同时占有对方需要的资源 而同时请求对方的资源,而它们在得到请求之前不会释放所占有的资源,那么就会导致死锁的发生,也就是进程不能实现同步。

下列有关在一个处理器(processor)上跑两个线程(thread)的说法中,正确的是?

  • A 一个线程可以改变另一个线程的程序计数器(program counter)
    B 一个线程既不能读也不能写另一个线程的栈(stack)
    C 一个线程可以读写另一个线程的寄存器(register)
    D 以上都不对

如何减少换页错误?

  • A 进程倾向于占用CPU
    B 访问局部性(locality of reference)满足进程要求
    C 进程倾向于占用I/O
    D 使用基于最短剩余时间(shortest remaining time)的调度机制

换页错误: Page Fault,其实应该翻译成缺页异常或缺页中断,是引入了虚拟内存后的一个概念。

虚拟内存: 操作系统启动后,在内存中维护着一个虚拟地址表,进程需要的虚拟地址在虚拟地址表中记录。一个程序被加载运行时,只是加载了很少的一部分到内存,另外一部分在需要时再从磁盘载入。当需要执行某条指令或使用某个数据,而发现它们并不在主存时,产生一个缺页中断,系统从辅存中把该指令或数据所在的页面调入内存。

B : 局部性好;导致下一个要找的东西就在当前内存的前后不远处,很有可能就在同一页里,所以就不需要频繁换页,换页错误也就少了。

AC 是改善性能。D也和减少换页错误无关

系统中内存不足程序所需大小,程序就无法执行。

  • A 错
    B 对

通常所说的"存储保护"的基本含义是()。

  • A 防止存储器硬件受损
    B 防止程序在内存丢失
    C 防止程序间相互越界访问
    D 防止程序被人偷看

在采用虚拟存储技术的存储系统中,一个多任务的操作系统经常将多个不同的程序同时调入主存执行;

  1. 首先需要保证这些程序间不会相互覆盖

  2. —个程序不能访问其他程序的数据以及不是分配给他的主存区域。

  3. 一个程序出错不会破坏其他用户程序和系统程序。

为了满足以上条件,采取一种限制方式保护主存中程序的技术被称为存储保护。存储区域保护主要是为了防止地址越界

下列进程调度算法中,()可能会出现进程长期得不到调度的情况(饥饿现象)。

  • A 非强占式静态优先权法
    B 强占式静态优先权法
    C 时间片轮转调度算法
    D 非强占式动态优先权法

强占式/抢占式
现行进程在运行过程中,如果有重要或紧迫的高优先级的进程到达(其状态必须为就绪),则现运行进程将被迫放弃处理机,系统将处理机立刻分配给新到达的进程。

静态优先权
创建进程时确定的,优先权在进程的整个运行期间保持不变。

动态优先权
在创建进程时所赋予的优先权,是可以随进程的推进或随其等待时间的增加而改变的,以便获得更好的调度性能,该优先权会随着等待的时间增长而增长。

A.非强占式静态优先权法:优先权不会变,假如有一个最低优先权的线程在等待,那么等优先级高的进程执行完就可以轮到该进程执行了。

B.强占式静态优先权法:强占式,说明高优先权的可以抢夺CPU的执行权。假如一个低优先权的执行,当一个高优先权的在就绪状态,那么CPU就会以抢占的方式执行高优先权的那个进程,低优先权的被抢占而处于就绪态。假如一直有高优先权的进程在就绪,那么低优先权的就有概念出现一直等待的情况。

C. 时间片轮转调度算法: 执行的时间片完毕后,被执行的进程会放到等待队列的队尾,一次循环,既然是循环的话,那就有机会轮到。

D.非强占式动态优先权法:非强占式的,但是如果一个进程一直在等待,那么他的优先权就会动态增长,就可以得到CPU的执行权

如果信号量的当前值为-4,则表示系统中在该信号量上有()个进程等待。

  • A 4
    B 3
    C 5
    D 0

这是信号量的P、V操作: p v操作又称 wait,signal,主要是操作进程中对进程控制的信息量的加减控制。

wait用法: wait(num) , num是目标参数

  • wait的作用是使信息量减一。如果信息量>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

signal用法: signa1(num) , num是目标参数

  • signal的作用是使信息量加一。如果信息量>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

信另量表示的是当前可用的资源个数,当信号量为负时,申请资源的进程就只能等待了。所以,信号量是负的多少,就表明有多少个进程申请了资源但无资源可用只能处于等待状态。

基址寻址方式中,操作数的有效地址是____。

  • A 基址寄存器内容加上形式地址(位移量)
    B 程序计数器内容加上形式地址
    C 变址寄存器内容加上形式地址
    D 寄存器内容加上形式地址

基址寻址方式,主要用来解决程序的动态定位问题。

基址寻址,将CPU中基址寄存器的内容,加上指令格式中的形式地址而形成操作数的有效地址。

本质上就是,我们通过基址寄存器+形式地址,形成一个内存的虚拟地址

若系统中只有用户级线程,则处理机调度单位是()。

  • A 线程
    B 进程
    C 程序
    D 作业

如果系统只有用户态线程,则线程对操作系统是不可见的,操作系统只能调度进程;
如果系统中有内核态线程,则操作系统可以按线程进行调度;

C:程序是静态的可执行文件,进程是动态的,可执行文件的内容通过拷贝到内存上占用CPU运行变成进程
D. 作业: 一个作业通常包括几个进程,几个进程共同完成一个任务,即作业。

一个在线服务器通常需要读取存储着海量数据的数据库。为了提高服务器处理速度,通常需要加cache(缓存),以下场景中不适合使用cache的是

  • A 数据库中每条数据被访问的概率近似相等,且独立
    B 使用了多线程机制的服务
    C 单条线程尺寸太小的数据
    D 有着大量访问的服务

因为数据库中每条数据被访问的概率近似相等,且缓存空问一般较小不可能存入较多的数据。无认预测所需要查询的数据,如果将被最近访问数据放入缓存用处不大,故而增加缓存起不到很大的作用。BCD都可以提高效率

在支持多线程的系统中,进程P创建的若干个线程不能共享的是( )。

  • A 进程 P 的代码段
    B 进程 P 中打开的文件
    C 进程 P 的全局变量
    D 进程 P 中某线程的栈指针

D 是线程私有的

操作系统中关于竞争和死锁的关系下面描述正确的是?

  • A 竞争一定会导致死锁
    B 死锁一定由竞争引起
    C 竞争可能引起死锁
    D 预防死锁可以防止竞争

死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  2. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

  3. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

A.竞争锁但不满足以上条件就不会引起死锁

B.线程A,线程B进行加锁:线程A拿到锁。但是忘解锁,又想要加锁,从而,线程A和线程B都陷入死锁状态。此时就没有竞争锁

C.竞争满足以上死锁的4个条件,就造成死锁,所以可能

D.如果有多个进程或线程对同一个资源的操作,那么就一定需要加锁,也就是一定涉及到竞争

并发是并行的不同表述,其原理相同。

  • A 错
    B 对

并发 : 多个进程在一个CPU下采用时间片轮转调度的方式,在一段时间之内,让多个进程都得以推进,称之为并发,CPU看是一个一个执行,但肉眼无法察觉。

并行 : 多个进程在多个CPU下分别,同时进行运行,这称之为并行。

线程的切换会引起进程的切换。

  • A 错
    B 对

一个进程也可能包含多个线程,对于同一个进程中的线程间切换,不会引起进程切换; 不同进程中的线程切换会导致进程切换。

操作系统的所有程序是常驻内存的。

  • A 错
  • B 对

对于正在等待事件的进程,可以将其换到外存(部分或全部),以空出更多地内存加载新的进程,使CPU资源充分被利用。

把逻辑地址转换程物理地址称为()。

  • A 地址分配
    B 地址映射
    C 地址保护
    D 地址越界

逻辑地址:内存中的虚拟地址。物理地址:内存条上真实的地址

A:进程创建时,分配内存

B:地址映射 : 为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址

C:多个进程之间,不能使用别人的内存

D:访问了其他进程的内存

在Unix系统中,处于()状态的进程最容易被执行。

  • A 辅存睡眠
    B 内存睡眠
    C 内存就绪
    D 辅存就绪

在这里插入图片描述

进程的控制信息和描述信息存放在()。

  • A JCB
    B PCB
    C AFT
    D SFT

为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process ControlBlock),它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。

PCB一般包括:

  1. 程序ID(PID、进程句柄)︰它是唯一的,一个进程都必须对应一个PID。PID一般是整形数字
  2. 特征信息:—般分系统进程、用户进程、或者内核进程等
  3. 进程状态:运行、就绪、阻塞,表示进程现的运行情况
  4. 优先级:表示获得CPU控制权的优先级大小
  5. 通信信息:进程之间的通信关系的反映,由于操作系统会提供通信道
  6. 现场保护区:保护阻塞的进程用
  7. 资源需求、分配控制信息

在()情况下,系统出现死锁。

  • A 若干进程因竞争资源而无休止地相互等待对方释放已占有的资源
    B 有多个封锁的进程同时存在
    C 计算机系统发生重大故障
    D 资源数大大小于进程数或进程同时申请的资源数大大超过资源总数

死锁是两个线程或者线程都在相互等待刘方释放资源,互不相让,就造成了死锁。

D:进程竞争资源失败而阻塞

当系统发生抖动(thrashing)时,可以采取的有效措施是( )。
Ⅰ.撤销部分进程 Ⅱ.增加磁盘交换区的容量 Ⅲ.提高用户进程的优先级

  • A 仅Ⅰ
    B 仅Ⅱ
    C 仅Ⅲ
    D 仅Ⅰ, Ⅱ

在具有对换功能的操作系统中,通常把外存分为文件区和对换区。前者用于存放文件,后者用于存放从内存换出的进程。

抖动现象是指刚刚被换出的页很快又要被访问。为此,又要换出其他页
而该页又快被访问,如此烦繁地置换页面,以致大部分时间都花在页面置换上。

Ⅰ:撤销部分进程可以减少所要用到的页面数,防止抖动。

Ⅱ和Ⅲ:对换区大小和讲程优先级都与抖动无关。页是虚拟内存相关,

下列关于线程说法错误的是()

  • A 耗时的操作使用线程,提高程序响应
  • B 耗内存的操作使用线程,提高内存利用率
  • C 多CPU的系统使用线程,提高CPU利用率
  • D 并行操作使用线程,如c/s架构中服务端程序为每个客户端请求创建一个线程来响应

B:线程是进程的一部分,提高内存利用率的说法错误

如果将固定块大小的文件系统中的块大小设置大一些,会造成()。

  • A 更好的磁盘吞吐量和更差的磁盘空间利用率
    B 更好的磁盘吞吐量和更好的磁盘空间利用率
    C 更差的磁盘吞吐量和更好的磁盘空间利用率
    D 更差的磁盘吞吐量和更差的磁盘空间利用率

文件是按块存储的,如果块大小设置的大一些,读取的时候一次性读取的就更多,磁盘吞吐量提升,但足文件可能不能占满整个块,导致利用率下降。

在下列进程的四个特征中,最基本的特征是()。

  • A 并发性
    B 动态性
    C 独立性
    D 异步性

A.并发性: 指多个进程实体同存于内存中,且在一段时间内同时运行。并发性是进程的重要特征,同时也成为操作系统的重要特征。

B.动态性: 进程的实质是进程实体的一次执行过程,因此,动态性是进程最基本的特征。

C.独立性: 进程实体是一个独立运行、独立分配资源和独立接受调度的基本单位。

D.异步性: 指进程按各自独立的、不可预知的速度,顺序向前推进,或者说实体按异步方式运行。

进程调度是从()选择一个进程投入运行。

  • A 就绪队列
    B 等待队列
    C 作业后备队列
    D 提交队列

B.等待队列: 用于使线程等待某一特定的事件发生而无需频繁的轮询,进程在等待期间睡眠,在某件事发生时由内核唤醒。

C.作业后备队列: 操作系统首先从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程插入就绪队列,准备执行。

下面有关Cache的说法哪一个是不正确的?

  • A 设置Cache的目的,是解决CPU和主存之间的速度匹配问题
    B 设置Cache的理论基础,是程序访问的局部性原理
    C Cache与主存统一编址,Cache的地址空间属于主存的一部分
    D Cache的功能均由硬件实现,对程序员是透明的

A.Cache出现的原因就是为了解决CPU与主存之间的速度匹配问题,CPU速度>Cache速度>主存速度。

B.程序访问的局部性原理是一个程序在运行的某一时段,它访问的代码或数据大部分是几种在集中在某一块区域的。

C. Cache的地址与主存的地址是两码事,不统一编址,也没有从属关系

D.cache是由硬件实现。

什么是内存抖动(Thrashing)( )

  • A 非常频繁的换页活动
    B 非常高的CPU执行活动
    C 一个极长的执行进程
    D 一个极大的虚拟内存

虚拟管理内存的概念,页面的频繁更换,导致整个系统效率急剧下降,这个现象称为内存抖动。
抖动一般是内存分配算法不好内存太小引或者程序的算法不佳引起的页面频繁从内存调入调出。

在所有非抢占CPU调度算法中,系统平均响应时间最优的是( )

  • A 实时调度算法
    B 短任务优先算法
    C 时间片轮转算法
    D 先来先服务算法

响应时间是任务到达和任务开始被处理(响应)之间的时间。周转时间是到达和处理结束之间的时间。

A**.实时调度算法:** 指系统能够在限定的响应时间内提供所需水平的服务。如果系统的时间约束条件得不到满足,将会发生系统出错。

B.短任务优先算法: 执行时间短的任务优先执行。

C.时间片轮转算法: 由系统调度就绪队列中的进程,每个进程分配一段时间片,利用时钟中断进行进程周期性切换。

D.先来先服务算法: 按先后顺序进行调度。

以上调度算法中,时间片轮转算法是抢占式调度算法;

在所有进程都几乎同时到达时,采用短任务优先调度算法的平均等待时间、平均周转时间最少

假设如下代码中,若t1线程在t2线程启动之前已经完成启动。代码的输出是()

public class TestDemo {
public static void main(String[]args)throws Exception {
final Object obj = new Object();
Thread t1 = new Thread() {
 public void run() {
     synchronized (obj) { // 1.
         try {
             obj.wait(); // 2. 释放对象锁,让当前线程等待(t1)
             // 6. t1 线程再申请 obj 锁, 申请成功,则继续往下地行
             System.out.println("Thread 1 wake up.");
         } catch (InterruptedException e) {
         }
     }
 }
};
t1.start();
Thread.sleep(1000);//We assume thread 1 must start up within 1 sec.
Thread t2 = new Thread() {
 public void run() {
     synchronized (obj) { // 3.
         obj.notifyAll(); // 4、通知 obj 对象 wait 等待的线程
         System.out.println("Thread 2 sent notify.");
     } // 5、这里释放锁 通知 obj
 }
};
t2.start();
}
}
  • A Thread 1 wake up Thread 2 sent notify.
    B Thread 2 sent notify. Thread 1 wake up
    C A、B皆有可能
    D 程序无输出卡死

以下哪句的说法是正确的?

  • A 在页式存储管理中,用户应将自己的程序划分为若干个相等的页
    B 所有的进程都挂起时,系统将陷入死锁
    C 执行系统调用可以被中断
    D 进程优先数是进程调度的重要依据,必须根据进程运行情况动态改变

A.页的划分是操作系统做的。

B.系统进入死锁必须满足4个条件:互斥、循环并等待、不剥夺、请求与保持;所有的进程都挂起,并不表示这些进程间有资源调用和循环等待的关系,有些进程定时器结束后可能自动唤醒。

C:用户程序调用系统函数,可以被优先级跟高的进程中断

D.有静态优先级调度。

下列方法中,____不可以用来程序调优?

  • A 改善数据访问方式以提升缓存命中率
    B 使用多线程的方式提高 I/O 密集型操作的效率
    C 利用数据库连接池替代直接的数据库访问
    D 利用迭代替代递归
    E 合并多个远程调用批量发送
    F 共享冗余数据提高访问效率

B 如果是 I/O 密集型应用 是可以的


编程题

剪花布条

一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?

输入描述:

输入包含多组数据。

每组数据包含两个字符串s,t,分别是成对出现的花布条和小饰条,其布条都是用可见ASCII字符表示的,可见的ASCII字符有多少个,布条的花纹也有多少种花样。花纹条和小饰条不会超过1000个字符长。

输出描述:

对应每组输入,输出能从花纹布中剪出的最多小饰条个数,如果一块都没有,那就输出0,每个结果占一行。

输入

abcde a3
aaaaaa aa

输出

0
3
import java.util.*;

public class Main {
    private static int cut(String s, String t) {
        int i = s.indexOf(t);
        if (i == -1) {
            return 0;
        }
        return 1 + cut(s.substring(i + t.length()), t);
    }
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String s = scanner.next();
            String t = scanner.next();
            System.out.println(cut(s, t));
        }
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String s = scanner.next();
            String t = scanner.next();
            int count = 0;
            while (s.contains(t)) {
                s = s.replaceFirst(t, ""); // replaceFirst 替换第一个
                count++;
            }
            System.out.println(count);
        }
    }
}

客似云来

NowCoder开了一家早餐店,这家店的客人都有个奇怪的癖好:他们只要来这家店吃过一次早餐,就会每天都过来;并且,所有人在这家店吃了两天早餐后,接下来每天都会带一位新朋友一起来品尝。
于是,这家店的客人从最初一个人发展成浩浩荡荡成百上千人:1、1、2、3、5……
现在,NowCoder想请你帮忙统计一下,某一段时间范围那他总共卖出多少份早餐(假设每位客人只吃一份早餐)。
输入描述:

测试数据包括多组。
每组数据包含两个整数from和to(1≤from≤to≤80),分别代表开店的第from天和第to天。

输出描述:

对应每一组输入,输出从from到to这些天里(包含from和to两天),需要做多少份早餐。
import java.util.*;
import java.math.*;

public class Main {
    public static void main(String[] args) {
        // 可以用 long[]
        // 1 1 2 3 5...
        BigDecimal[] bigDecimal = new BigDecimal[80];
        bigDecimal[0] = new BigDecimal("1");
        bigDecimal[1] = new BigDecimal("1");
        for (int i = 2; i < 80; i++) { // 数组 第 3 个下标为 2
            bigDecimal[i] = bigDecimal[i - 1].add(bigDecimal[i - 2]);
        }
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            BigDecimal sum = new BigDecimal("0");
            for (int i = from - 1; i < to; i++) {
                sum = sum.add(bigDecimal[i]); // sum = 
            }
            System.out.println(sum);
        }
    }
}

收件人列表

NowCoder每天要给许多客户写电子邮件。正如你所知,如果一封邮件中包含多个收件人,收件人姓名之间会用一个逗号和空格隔开;如果收件人姓名也包含空格或逗号,则姓名需要用双引号包含。
现在给你一组收件人姓名,请你帮他生成相应的收件人列表。
输入描述:

输入包含多组数据。

每组数据的第一行是一个整数n (1≤n≤128),表示后面有n个姓名。

紧接着n行,每一行包含一个收件人的姓名。姓名长度不超过16个字符。

输出描述:

对应每一组输入,输出一行收件人列表。

输入

3
Joe
Quan, William
Letendre,Bruce
2
Leon
Kewell

输出

Joe, "Quan, William", "Letendre,Bruce"
Leon, Kewell
import java.util.*;

public class Main {
    public static void main(String[]  args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
//            String ret = scanner.nextLine(); // n 读一行 防止换行作为了一个 s
//            int n = Integer.valueOf(ret);
            int n = scanner.nextInt();
            scanner.nextLine(); // 或者再读 line
            while (n-- != 0) {
                String s = scanner.nextLine();
                if (s.contains(",") || s.contains(" ")) {
                    if (n == 0) { // 最后一个没有逗号
                        System.out.print("\"" + s + "\"");
                    } else {
                        System.out.print("\"" + s + "\"" + ", ");
                    }
                } else {
                    if (n == 0) {
                        System.out.print(s);
                    } else {
                        System.out.print(s + ", ");
                    }
                }
            }
            System.out.println();
        }
    }
}

养兔子

一只成熟的兔子每天能产下一胎兔子。每只小兔子的成熟期是一天。 某人领养了一只小兔子,请问第N天以后,他将会得到多少只兔子。

输入描述:

测试数据包括多组,每组一行,为整数n(1≤n≤90)。

输出描述:

对应输出第n天有几只兔子(假设没有兔子死亡现象)。

输入

1<br/>2

输出

1<br/>2
import java.util.*;
import java.math.*;

public class Main {
    public static void main(String[] args) {
        // 可以用 long
        // 此题是 1 2 3 5...
        BigDecimal[] bigDecimal = new BigDecimal[90];
        BigDecimal b1 = new BigDecimal("1");
        BigDecimal b2 = new BigDecimal("1");
        BigDecimal b3 = b1;
        for (int i = 0; i < 90; i++) {
            bigDecimal[i] = b3;
            b1 = b2;
            b2 = b3;
            b3 = b1.add(b2);
        }
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            System.out.println(bigDecimal[n - 1]);
        }
    }
}

安信奇 第五个月生兔子

有一对兔子,从出生后第五个月起每个月都生一对兔子,小兔子长到第五个月后每个月又生一对兔子,假如兔子都不死,问第n个月的兔子对数为多少。

对数: 1  1  1  1  2  3  4  5  7  10  14  19  26  36  50  69

增数: 0  0  0  0  1  1  1  1  2   3    4    5    7   10  14  19

代码:

public class TestDemo {
    private static int fib2(int n) {
        if (n == 1 || n == 2 || n == 3 || n == 4) {
            return 1;
        }
        return fib2(n - 1) + fib2(n - 4);
    }

    private static void fib(int n) {
        int[] array = new int[n];
        for (int i = 0; i < 4; i++) {
            array[i] = 1;
        }
        for (int i = 4; i < n; i++) {
            array[i] = array[i - 1] + array[i - 4];
        }
        System.out.println(Arrays.toString(array));
    }

    public static void main(String[] args) {
        fib(16); // [1, 1, 1, 1, 2, 3, 4, 5, 7, 10, 14, 19, 26, 36, 50, 69]
    }
}

年会抽奖——错排问题

今年公司年会的奖品特别给力,但获奖的规矩却很奇葩:
\1. 首先,所有人员都将一张写有自己名字的字条放入抽奖箱中;
\2. 待所有字条加入完毕,每人从箱中取一个字条;
\3. 如果抽到的字条上写的就是自己的名字,那么“恭喜你,中奖了!”
现在告诉你参加晚会的人数,请你计算有多少概率会出现无人获奖?
输入描述:

输入包含多组数据,每组数据包含一个正整数n(2≤n≤20)。

输出描述:

对应每一组数据,以“xx.xx%”的格式输出发生无人获奖的概率。

输入

2

输出

50.00%

【解题思路】

在这里插入图片描述

import java.util.*;

public class Main {
    public static void main(String[] args) {
        long[] d = new long[21]; // 第 n 个人错排总数
        d[0] = 0;
        d[1] = 0;
        d[2] = 1;
        long[] f = new long[21]; // n 的阶乘
        f[0] = 1;
        f[1] = 1;
        f[2] = 2;
        for (int i = 3; i <= 20; i++) {
            d[i] = (i - 1) * (d[i - 1] + d[i - 2]);
            f[i] = f[i - 1] * i;
        }
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextInt()) {
            int n = scanner.nextInt();
            double ans = 100.0 * d[n] / f[n];
            System.out.printf("%.2f%%\n", ans);
        }
    }
}

抄送列表

NowCoder每天要处理许多邮件,但他并不是在收件人列表中,有时候只是被抄送。他认为这些抄送的邮件重要性比自己在收件人列表里的邮件低,因此他要过滤掉这些次要的邮件,优先处理重要的邮件。
现在给你一串抄送列表,请你判断目标用户是否在抄送列表中。
输入描述:

输入有多组数据,每组数据有两行。

第一行抄送列表,姓名之间用一个逗号隔开。如果姓名中包含空格或逗号,则姓名包含在双引号里。总长度不超过512个字符。

第二行只包含一个姓名,是待查找的用户的名字(姓名要完全匹配)。长度不超过16个字符。

输出描述:

如果第二行的名字出现在收件人列表中,则输出“Ignore”,表示这封邮件不重要;否则,输出“Important!”,表示这封邮件需要被优先处理。

输入

Joe,Kewell,Leon
Joe
"Letendre, Bruce",Joe,"Quan, William"
William

输出

Ignore
Important!
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String s = scanner.nextLine();
            String name = scanner.nextLine();
            boolean flag = true;
            for (int i = 0; i < s.length(); i++) {
                StringBuilder sb = new StringBuilder();
                if (s.charAt(i) == '\"') { // 名字里有空格或逗号 找到另一个双引号
                    i++; // 跳过此双引号
                    while (i < s.length() && s.charAt(i) != '\"')
                        sb.append(s.charAt(i++));
                } else { // 普通名字 找到逗号为之
                    while (i < s.length() && s.charAt(i) != ',') {
                        sb.append(s.charAt(i++));
                    }
                }
                if (sb.toString().equals(name)) {
                    System.out.println("Ignore");
                    flag = false;
                    break;
                }
            }
            if (flag) {
                System.out.println("Important!");
            }
        }
    }
}

Rational Arithmetic (20)

For two rational numbers, your task is to implement the basic arithmetics, that is, to calculate their sum, difference,
product and quotient.

输入描述:

Each input file contains one test case, which gives in one line the two rational numbers in the format "a1/b1 a2/b2". 
The numerators and the denominators are all in the range of long int. If there is a negative sign, it must appear only in 
front of the numerator. The denominators are guaranteed to be non-zero numbers.

输出描述:

For each test case, print in 4 lines the sum, difference, product and quotient of the two rational numbers, respectively. The format of each 
line is "number1 operator number2 = result". Notice that all the rational numbers must be in their simplest form "k a/b", where k is 
the integer part, and a/b is the simplest fraction part. If the number is negative, it must be included in a pair of parentheses. If the 
denominator in the division is zero, output "Inf" as the result. It is guaranteed that all the output integers are in the range of long int.

输入

5/3 0/6

输出

1 2/3 + 0 = 1 2/3<br/>1 2/3 - 0 = 1 2/3<br/>1 2/3 * 0 = 0<br/>1 2/3 / 0 = Inf

【解析】

1、输出要包含4行:分别是两个分数的加减乘除,
比如:
a+ b = c
a - b = ca * b = ca / b = c

2、每部分结果必须要是最简的形式:不能出现2/4
4===>1/2。最简形式——除上最大公约数即可

3、如果是负数,必须要输出负号

4、如果分母是0,则输出Inf

5、分子和分母都在long int的范围之内

例:——a/b + c/d = (a*d + b*c)/(b*d) 加 减 通分即可
2/3 + 2/5 = (2*5 + 2*3)/(3*5)

——a/b - c/d = (a*d - b*c)/(b*d)
2/3 - 2/5 = (2*5 - 2*3)/(3*5)

——a/b * c/d = (a*c) / (b*d) 分子乘分子,分母乘分母
2/3 * 2/5 = (2*2) / (3*5)

——a/b / c/d = (a*d) / (b*c) 除 等同于 乘倒数
2/3 / 2/5 = (2*5) / (3*2) = 10/6 —>化简

import java.util.*;

class Rational {
    private long numerator; // 分子
    private long denominator; // 分母
    private long integer; // 带分数的整数部分
    private boolean negative; // 负数
    private boolean isZero; // 分母为 0
    private long totalNumerator; // 参与运算的分子:带分数的整数部分 + 原来分子

    // 截取 分子部分
    protected static long parseNumerator(String s) {
        return Long.parseLong(s.substring(0, s.indexOf('/')));
    }

    // 截取 分母部分
    protected static long parseDenominator(String s) {
        return Long.parseLong(s.substring(s.indexOf('/') + 1, s.length()));
    }

    // 判断化简 分子 分母
    protected Rational(long n, long d) {
        // 在输入的时候分母永远不可能为0,但是经过计算之后分母可能会为0
        if (0 == d) {
            isZero = true;
            return;
        }
        // If there is a negative sign, it must appear only in front of the numerator.
        if (n < 0) {
            negative = true;
        }
        // 在输入的时候,分母永远不可能小于0,但是经过计算之后分母也可能会小于0
        if (d < 0) {
            negative = !negative;
        }
        // 如果输入是假分数,需要将其调整为真分数 --> 5 / 3 --> 1 2/3
        integer =  n / d;
        numerator = n - integer * d;
        denominator = Math.abs(d);
        // 如果分数不是最简的形式,需要将其约分为最简的形式,比如:10 / 15
        // 在分子和分母的基础之上分别处以分子和分母的最大公约数
        if (numerator > 1 || numerator < -1) {
            long gcd = calcGCD(Math.abs(numerator), denominator); // 用整数
            if (gcd > 0) { // 有最大公约数
                numerator /= gcd;
                denominator /= gcd;
            }
        }
        // 用于计算的分子 如果是假分数,化成真分数计算
        totalNumerator = numerator + integer * denominator;
    }

    // 最大公约数 辗转相除法
    private long calcGCD(long n1, long n2) {
        if (n2 == 0) {
            return n1;
        }
        return calcGCD(n2, n1 % n2);
    }

    // 计算
    protected Rational Add(Rational r) { // 通分
        long n = totalNumerator * r.denominator + r.totalNumerator * denominator; // 分子
        long d = denominator * r.denominator; // 分母
        return new Rational(n, d); // 结果同样是最简加假分式
    }

    protected Rational Sub(Rational r) { // 通分
        long n = totalNumerator * r.denominator - r.totalNumerator * denominator;
        long d = denominator * r.denominator;
        return new Rational(n, d);
    }

    protected Rational Mul(Rational r) {
        long n = totalNumerator * r.totalNumerator;
        long d = denominator * r.denominator;
        return new Rational(n, d);
    }

    protected Rational Div(Rational r) {
        long n = totalNumerator * r.denominator;
        long d = denominator * r.totalNumerator;
        return new Rational(n, d);
    }

    // 判断输出形式
    public String toString() {
        if (isZero) { // 分母为 0 输出 Inf
            return "Inf";
        }
        if (integer == 0 && numerator == 0) { // 结果为 0
            return "0";
        }
        StringBuilder stringBuilder = new StringBuilder();
        if (negative) { // 负数
            stringBuilder.append("(-");
        }
        // 输出 Rational:整数部分 + 分数部分
        // 整数部分
        if (0 != integer) {
            stringBuilder.append(Math.abs(integer));
            if (0 != numerator) { // 有分数 和整数之间有空格
                stringBuilder.append(" ");
            }
        }
        // 分数部分 可能没有
        if (0 != numerator) {
            stringBuilder.append(Math.abs(numerator) + "/" + denominator);
        }
        if (negative) {
            stringBuilder.append(")");
        }
        return new String(stringBuilder);
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            // 读取两个分数 分别截取出各自的 分子 和 分母
            String s = scanner.next(); //
            Rational r1 = new Rational(Rational.parseNumerator(s), Rational.parseDenominator(s));
            s = scanner.next(); //
            Rational r2 = new Rational(Rational.parseNumerator(s), Rational.parseDenominator(s));
            // 输出 两个分数 运算符 计算的结果
            // ——> 5/3   3/2
            // 1 2/3 + 1 1/2 = 3 1/6
            System.out.println(r1 + " + " + r2 + " = " + r1.Add(r2));
            System.out.println(r1 + " - " + r2 + " = " + r1.Sub(r2));
            System.out.println(r1 + " * " + r2 + " = " + r1.Mul(r2));
            System.out.println(r1 + " / " + r2 + " = " + r1.Div(r2));
        }
    }
}

Pre-Post

We are all familiar with pre-order, in-order and post-order traversals of binary trees. A common problem in data structure classes is to find the pre-order traversal of a binary tree when given the in-order and post-order traversals. Alternatively, you can find the post-order traversal when given the in-order and pre-order. However, in general you cannot determine the in-order traversal of a tree when given its pre-order and post-order traversals. Consider the four binary trees below:

img All of these trees have the same pre-order and post-order traversals. This phenomenon is not restricted to binary trees, but holds for general m-ary trees as well.

输入描述:

Input will consist of multiple problem instances. Each instance will consist of a line of the form m s1 s2, indicating that the trees are m-ary trees, s1 is the pre-order traversal and s2 is the post-order traversal.All traversal strings will consist of lowercase alphabetic characters. For all input instances, 1 <= m <= 20 and the length of s1 and s2 will be between 1 and 26 inclusive. If the length of s1 is k (which is the same as the length of s2, of course), the first k letters of the alphabet will be used in the strings. An input line of 0 will terminate the input.

输出描述:

For each problem instance, you should output one line containing the number of possible trees which would result in the pre-order and post-order traversals for the instance. All output values will be within the range of a 32-bit signed integer. For each problem instance, you are guaranteed that there is at least one tree with the given pre-order and post-order traversals.

输入

2 abc cba
2 abc bca
10 abc bca
13 abejkcfghid jkebfghicda

输出

4
1
45
207352860

【解析】

每组测试用例:m s1s2
m 表示 树为 m叉树,s1 表示前序遍历结果,s2 表示中序遍历结果,s1 和 s2 长度相同,而且里面包含小写字母

一个测试用例占一行,最后一个测试用例之后的一行为 0
输出:每个用例输入所有可能性的树的个数,并且输出一行,结果一定不会超过unsigned long

在这里插入图片描述

**——分离根节点有多少颗子树:**

在这里插入图片描述

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 保存子树的前序 后序 遍历结果
class SubTree {
    String pre;
    String post;
    public SubTree(String pre, String post) {
        this.pre = pre;
        this.post = post;
    }
}

public class Main {
    // 计算 n 的阶乘
    private static long fac(int n) {
        long f = 1;
        for (int i = 1; i <= n; i++) {
            f *= i;
        }
        return f;
    }

    // 计算组合
    private static long calcCom(int n, int m) {
        m = m < (n - m) ? m : (n - m); // C(n, m) = C(n, n-m)
        long r = 1;
        for (int i = n; i >= n - m + 1; i--) {
            r *= i;
        }
        return r / fac(m);
    }

    // 分离根节点有多少颗子树
    private static List<SubTree> calcSubTree(String pre, String post) {
        int subRootPreInx = 1; // 子树的根在前序遍历结果中的位置   根--->子树1--->子树2--->...--->子树n
        int postFirst = 0; // 后序遍历 : 子树1-->子树2-->...--->子树n-->根节点
        List<SubTree> list = new ArrayList<>();
        while (subRootPreInx < pre.length()) {
            char rootSub = pre.charAt(subRootPreInx); // 确认该颗子树的根节点 从前序遍历中找根
            int subRootPostInx = post.indexOf(rootSub); // 从后序遍历中找根的位置
            int subTreeNodeCount = subRootPostInx - postFirst + 1; // 该颗子树中总共有多少个节点
            SubTree subTree = new SubTree(
                    pre.substring(subRootPreInx, subRootPreInx + subTreeNodeCount),
                    post.substring(postFirst, postFirst + subTreeNodeCount)
            ); // 从前序遍历和后序遍历结果中分离出该棵子树的 前序和后序遍历结果
            list.add(subTree); // 保存 子树的 前序和后序遍历结果
            subRootPreInx += subTreeNodeCount; // 分离下一颗子树
            postFirst += subTreeNodeCount;
        }
        return list;
    }

    private static long calcPossibilitiesOfTree(int n, String pre, String post) {
        if (pre.isEmpty() || pre.length() == 1) { // 没有节点 或 只有一个节点
            return 1;
        }
        List<SubTree> subTree = calcSubTree(pre, post); // 分离根节点有多少颗子树
        long result = calcCom(n, subTree.size()); // 根节点子树 的可能性的 组合 结果
        for (SubTree e : subTree) { // 递归计算 根的子树 分别有多少种可能性
            result *= calcPossibilitiesOfTree(n, e.pre, e.post);
        }
        return result;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            if (n == 0) { // An input line of 0 will terminate the input.
                break;
            }
            String pre = scanner.next();
            String post = scanner.next();
            long ret = calcPossibilitiesOfTree(n, pre, post);
            System.out.println(ret);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三春去后诸芳尽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值