Java最新面试题及答案

2022最新Java面试题及答案参考。喜欢的朋友点个赞吧。欢迎收藏、评论区留言、顶贴!

某大型银行1面

1.你们公司rpc框架涉及哪些基础组件,讲讲整个运行流程
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息实体;
3)client stub找到服务地址,并将消息发送到服务方;
4)服务提供方server 端收到消息后进行解码;
5)server 端根据解码结果调用本地接口;
6)server 端将本地接口调用结果组装成消息并发送至消费方;
7)client stub接收到消息,并进行解码、反序列化;
8)服务消费方得到最终结果。

注册和发现组件:consul、zk
通信组件:Netty4
序列化框架:protobuf ,dubbo3才引入了此特性
接口契约:proto文件 ,dubbo3才引入了此特性
传输层协议:自定义的,基于 LengthFieldBasedFrameDecoder
每个网络包分为3部分:8字节的固定头部+protobuff形式的扩展包头+protobuff形式的包体(包体可选);dubbo3除了自身的dubbo协议还兼容http2

2.ReentrantLock是如何实现可重入的
ReentrantLock底层是基于AQS的。AQS通过一个int类型的state状态变量和一个FIFO队列来完成共享资源的获取,比如线程的排队,阻塞,唤醒等。如果state=0,则表示此时还没有线程获取到资源,通过CAS操作将其设置为1,设置成功则表示该线程获取到排它锁了。如果state变量>0,则表明有线程已经获取到锁了,此时判断一下是否是当前线程抢占的锁,如果是,则state变量累加,重入成功。否则获取锁失败。state的值即为线程重入的次数。锁释放的时候state会减一。
公平锁的话多了一个判断是否有排队线程的逻辑。判断:是否有比自己申请早的线程在同步队列中等待,若有,去等待;若没有,才允许去抢占。
synchronized和ReentrantLock都是可重入的。
synchronized是Java原生的互斥同步锁,使用方便,对于synchronized修饰的方法或同步块,无需显式释放锁。
synchronized底层是通过monitorenter和monitorexit两个字节码指令来实现加锁解锁操作的。而ReentrantLock做为API层面的互斥锁,需要显式地去加锁解锁。

3.mysql int(20) 括号中数值的作用
括号里面的数字表示显示宽度,不是代表能存放多少位数。可以填充0。
int表示的范围是一个正常大小整数,即4个字节。有符号的int范围是-2^31到 2^31 - 1(21亿多)的整型数据,无符号的范围是0到2^32 - 1。
bigint:8 个字节,-2^63 到 2^63-1 ,无符号的范围是0到2^64 - 1。
tinyint:有符号的范围是-128 - 127,无符号的范围是 从 0 到 255 的整型数据。一位大小为 1 字节。

4.mysql 执行了create index或者ALTER TABLE t1 ADD INDEX之后底层做的事情
1)读取索引列的数据
2)将数据按照索引列排序
3)构建B+树索引,往B+树中添加节点元素(自下而上构建索引)

5.B+树和平衡树的区别,以及为什么mysql不用红黑树
平衡二叉树叶子节点的高度差绝对值<=1,且左右子树都是一颗平衡二叉树。插入、删除、查找的复杂度为O(logn)。常见的平衡二叉搜索树有AVL树和红黑树(非严格意义上的平衡二叉树)。
mysql支持的索引类型:B+树索引、哈希索引、全文索引
hash索引是散列的分布方式,不支持范围查找和排序。全文索引对中文的支持不够好。
hash索引适用于不需要做排序、范围查询的需求。mysql innoDB内部会采用一个自适应的hash索引,即innoDB检测到某些索引值使用频繁时,会在内存中基于B+树索引之上再创建一个hash索引,这就让B+树索引具备一些hash索引的优点。
B+树索引数据库中使用的最为典型的索引结构。B是代表平衡(balance)。从平衡二叉树演化而来,但是B+树不是一个二叉树。
B+树索引可以分为:clustered index和non-clustered index 。区别是:叶子节点存放的是否是一整行的信息。叶子节点保存所有数据,每个叶子节点通过双向链表连接,便于范围查询。
当通过非聚集索引查询数据时,如果查询的列没有包含在索引项里,则会先通过非聚集索引找到指向聚集索引的主键,再通过主键索引来找到完整的记录。

为什么主键索引的索引项的值越短越好,且最好单调递增?
因为辅助索引的叶子结点存储的是键值对应的数据的主键键值。因此主键的值长度越小占用空间越小,检索效率更好。非单调的主键在插入的时候为了维持B+树的特性会平凡的分裂调整,十分低效。因此使用自增主键是个不错的选择。

为什么mysql不用红黑树或者其它二叉树之类的?
索引存在的目的就是为了加速查询速度,所以哪一种数据结构的查询速度更快,MySQL就使用哪种。
普通二叉树:不具有平衡性,如果索引列是递增的,可能导致索引的数据结构变成链表的形式(就算不是链表也会由于树的高度很高,导致多次的磁盘I/O,从而影响性能),查询效率极低
红黑树:是一种平衡的二叉树,事件复杂度是(Ologn),在大数据量下(百万千万级别下),I/O的次数也是非常多的,查询效率也不高。因为一个节点只存储一个元素,导致数据量越多树的高度越高,查询时磁盘I/O次数增多。
B+树:一个节点可以存储多个数据元素,节点的元素个数称为节点的度。通常一棵B+树的度都会超过100,因此使得树的高度非常矮,那么查询时IO的次数就非常少。
B+树节点元素的数据类型是key-value,其中key代表添加索引的列的值,value代表数据库一行数据的指针。
由于B+Tree非叶子节点不存储数据(data),因此所有的数据都要查询至叶子节点,而叶子节点的高度都是相同的,因此所有数据的查询速度都是一样的,查询性能比较稳定。

B+Tree 相对于 B-Tree的改进
1) B-Tree的所有节点都存储了 data 元素, B+Tree的非叶子节点不存储 data元素,因此 B+Tree 的一个非叶子节点可以存储更多的索引;
2)B+Tree在叶子节点之间增加了双向指针连接;对范围查找有很好的支持;

6.jdk8流式编程为什么性能高
流的中间操作都会返回流对象本身,不会立即计算,而是形成一个调用链,只有最后执行Terminal操作时才会触发计算,这是一种惰性(lazy)计算的思想。多个转换操作在Terminal 操作的时候融合起来,一次循环完成计算。

7.网关tps,tomcat单机tps
一般没有只对网关做过压测,都是具体到后面的某个业务接口。一般业务处理的复杂程度不同、硬件配置和线程配置不同,结果也不同。2c4G单机tps普遍能达到上千。如果业务读写数据库频繁,低于1000也有可能;如果业务非常简单,比如网关这种透传的,单机上万qps也能达到。

8.redis 哨兵机制 如何实现故障转移,数据一致性
Redis Sentinel是一个分布式系统,为Redis提供高可用性解决方案。可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来 接收关于Master服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个Slave服务器作为新的Master服务器。
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance) 该系统执行以下三个任务:
1)监控(Monitoring): Sentinel 会不断地定期检查你的主服务器和从服务器是否运作正常。
2)提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
3)自动故障迁移(Automaticfailover): 当一个Master服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;
当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。
有足够数量的 Sentinel(大于等于配置文件指定的值,一般是半数以上)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
如果Master节点被判定为客观下线之后,就要选取一个哨兵节点来完成后面的故障转移工作,获得半数以上投票的将成为领导者。

9.tcp的拆包与粘包
TCP是基于字节流传输的,是没有边界的一串…0101…这种二进制流。粘包出现的根本原因是不确定消息的边界。接收端在面对无边无际的二进制流的时候,根本不知道收了多少 个01 才算一个消息。读多了就导致粘包
因为TCP是面向字节流的,没有明确的边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。
如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。
如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。
UDP是基于数据报的通信协议,每个数据报有消息边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。

关闭 Nagle 算法就不会粘包了吗?
Nagle 算法的目的是为了避免发送小的数据包。
Nagle 算法开启时,如果包长度达到MSS(最大报文长度)或含有Fin包,立刻发送,否则等待下一个包到来;如果下一包到来后两个包的总长度超过MSS的话,就会进行拆分发送;因此Nagle 算法会产生拆包粘包。
但是Nagle 算法是早期发明的,现在的实际应用中一般都会关闭Nagle 算法。就算关闭 Nagle 算法,接收数据端的应用层没有及时读取 TCP Recv Buffer(缓冲区) 中的数据,还是会发生粘包。

如何解决粘包、拆包?
只要在发送端每次发送消息的时候给消息带上识别消息边界的信息,接收端就可以根据这些信息识别出消息的边界,从而区分出每个消息。
1)加入特殊标记
比如加入消息的头尾标志,比如通过回车换行符作为分界
2)加入消息长度信息
比如在收到头标志时,里面还可以带上消息长度,以此表明在这之后多少 byte 都是属于这个消息的。如果在这之后正好有符合长度的 byte,则取走,作为一个完整消息给应用层使用。

Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器(Decoder)来解决粘包和拆包的问题。如:

  • LineBasedFrameDecoder:以行为单位进行数据包的解码;
  • DelimiterBasedFrameDecoder:以特殊的符号作为分隔来进行数据包的解码;
  • FixedLengthFrameDecoder:以固定长度进行数据包的解码;
  • LenghtFieldBasedFrameDecode:适用于消息头包含消息长度的协议(最常用);
    基于Netty进行网络读写的程序,可以直接使用这些Decoder来完成数据包的解码。对于高并发、大流量的系统来说,每个数据包都不应该传输多余的数据(所以补齐的方式不可取),LenghtFieldBasedFrameDecode更适合这样的场景。

10.TCP的3次握手,4次挥手
socket 编程中,客户端执行 connect() 时。将触发三次握手。
第一次握手:客户端向服务端发起连接建立请求,请求包含标志位SYN=1和一个随机序列号seq=J。发送完毕后客户端进入SYN_SENT状态。
第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都置为1,设置响应序号ack=J+1,并随机产生一个序号值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack序号是否为J+1,ACK标志位是否为1,如果正确则将标志位ACK置为1,并生成响应序号ack=K+1,并将该数据包发送给服务器端,服务器端检查ack序号是否为K+1,ACK标志位是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
小写的ack是对上一个包的序号进行确认的号,是上一个号+1;大写的ACK是TCP首部标志位,用于标志TCP的包是否对上一个包进行了确认。

常见的标志位:SYN(同步标志位)、ACK(确认标志位)、FIN(完成标志位)

TCP 4次挥手关闭连接
在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
第二次分手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入FIN_WAIT_2状态,Server端告诉Client端,我确认并同意你的关闭请求。
第三次分手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
第四次分手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了。

为什么连接的时候是三次握手,关闭的时候却是四次握手?
建立连接时因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。所以建立连接只需要三次握手。
由于TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议,TCP是全双工模式。
这就意味着,关闭连接时,当Client端发出FIN报文段时,只是表示Client端告诉Server端数据已经发送完毕了。当Server端收到FIN报文并返回ACK报文段,表示它已经知道Client端没有数据发送了,但是Server端还是可以发送数据到Client端的,所以Server很可能并不会立即关闭SOCKET,直到Server端把数据也发送完毕。
当Server端也发送了FIN报文段时,这个时候就表示Server端也没有数据要发送了,就会告诉Client端,我也没有数据要发送了,之后彼此就会愉快地中断这次TCP连接。

为什么要等待2MSL?
MSL:(Maximum Segment Lifetime)报文最长存活时间,它是任何报文段被丢弃前在网络内的最长时间。
有以下两个原因:
第一点:保证TCP协议的全双工连接能够可靠关闭
由于IP协议的不可靠性或者是其它网络原因,导致了Server端没有收到Client端的ACK报文,那么Server端就会在超时之后重新发送FIN,如果此时Client端的连接已经关闭处于CLOESD状态,那么重发的FIN就找不到对应的连接了,从而导致连接错乱,所以,Client端发送完最后的ACK不能直接进入CLOSED状态,而要保持TIME_WAIT,当再次收到FIN的收,能够保证对方收到ACK,最后正确关闭连接。
第二点:保证这次连接的重复数据段从网络中消失
如果Client端发送最后的ACK直接进入CLOSED状态,然后又再向Server端发起一个新连接,这时不能保证新连接的与刚关闭的连接的端口号是不同的,也就是新连接和老连接的端口号可能一样了,那么就可能出现问题:如果前一次的连接某些数据滞留在网络中,这些延迟数据在建立新连接后到达Client端,由于新老连接的端口号和IP都一样,TCP协议就认为延迟数据是属于新连接的,新连接就会接收到脏数据,这样就会导致数据包混乱。所以TCP连接需要在TIME_WAIT状态等待2倍MSL,才能保证本次连接的所有数据在网络中消失。

11.讲讲SpringBoot的启动流程。


某大型金融机构1面

1.分布式锁和分布式事务你们一般如何实现的?
参考:《分布式一致性实战》

2.你们服务的TPS能达到多少?达到这个值之后压不上去的原因是什么?瓶颈在哪?不要跟我讲什么内存、CPU限制之类大众化的回答!
谈吐间,隐约觉得面试官吊炸了…

3.你们消息服务有做限流和熔断吗?如果某一个时刻海量消息推送请求过来会不会垮掉?有没有什么保护机制?

4.JAVA的内存模型了解吗?happen-before原则是怎样的?

5.平时线上问题处理过么?如何定位问题的,比如CPU使用率偏高一般什么原因,如何解决的?

… 欢迎评论区留言补充!


某大型银行2-3面

1.你们公司的RPC框架有什么优点?用到Java的SPI机制了么?如何实现优雅停机的,讲讲
优点:轻量级,无需依赖web容器,基于Netty实现TCP和HTTP双协议支持。使用google protocbuffer 高性能。基于idl(接口定义语言)作为前后端的接口契约,即使用protoc文件定义服务接口。

用到了SPI扩展机制。比如提供了负载均衡策略(算法)的扩展机制,基于JAVA SPI 机制。
用户可以自已实现负载均衡的SPI接口,编写负载均衡插件(spi接口的实现类),将实现类的全路径名配置到META-INF/services 中以接口全路径名命名的配置文件中。在application.properties文件配置负载均衡策略为自定义的负载均衡实现类即可使用。

优雅停机可以通过监听Spring容器的关闭事件来做资源回收。或者自行实现JVM的关闭钩子。

2.你们项目中异步http客户端和同步的比如resttemplate有什么区别,何为异步,性能是否有提升?
参见 https://blog.csdn.net/hellozpc/article/details/122441522

3.动态代理有哪些不同的织入时机,各有什么优缺点,了解么?你们用的什么动态代理技术?
主要分为编译时和运行时动态织入。编译时织入性能高,因为是编译好的。但是不具备跨平台性,也不够灵活。动态方式更加灵活,跨平台,但是有运行时性能消耗。我们框架中用的是javaasist。(欢迎补充)

4.http协议各个版本的改进点了解么,连接复用(keepalive)和io多路复用有什么区别?
一个TCP连接上并行发送http请求时,消息的先后顺序如何处理的?
参见 https://fafucoder.github.io/2020/09/14/linux-http2/
参见《http对线面试官》

5.你这个项目中同一个用户的数据每次都发送到同一台机器是如何做到的?服务是集群部署的吧?怎么保证同一个用户每次的http请求落到同一台机器?机器扩容之后怎么办?
我们RPC框架支持一致性hash负载均衡策略,可以设置使用这种路由策略。比如根据用户id做一致性hash。扩容时要么做好数据迁移工作,要么进行数据兜底,比如每次http接口请求处理好的数据放到redis中或者DB中。扩容后没有命中内存数据时可以从redis或者db取。当然,如果业务本身可以容忍数据丢失就更好了,既不需要访问redis,也不需要访问db,丢就丢了,不影响接口的处理逻辑。

6.你们项目中用的什么消息队列?能讲讲Kafka中的partition和副本的概念吗?Kafka副本之间是如何同步的?如果Leader副本挂了怎么办?
参见:《Kafka面试题》

7.Java中的锁用过吧?synchronized和Lock锁有什么区别?底层实现机制了解吗?sleep和wait的区别?
参见:《JAVA并发编程》

8.你们kafka消息遇到过重复消费的场景么,如何避免重复消费的?
生产端可能失败重试;
消费端异常导致提交offset失败,没有提交offset,下次还会重复拉取。
Kafka本身也支持幂等,最好还是消费端做好业务幂等控制。
幂等实现方案:《常见的幂等设计方案

9.jdk8中HashMap的改进
红黑树、头插改尾插
HashMap在JDK1.8版本经过优化之后,整体的数据结构变成了数组+链表+红黑树这样的形式。而尾插法说的就是在往HashMap里面put元素时,数组桶位上面还是未转化为红黑树的链表时,新增在链表上元素的位置为链表尾部,故名尾插法。

10.讲讲JVM内存模型(PS:注意和JVM运行时数据区不是一个概念)
参见《JAVA并发编程》

11.Java 虚拟机栈的构成能讲讲吗?
参见《JVM的体系结构》

12.你平时工作中遇到过内存溢出等线上问题吗?讲讲如何发现和解决的?
参见《线上JVM内存溢出问题定位》

13.技术和业务你更偏向哪一种,如果两者选一种的话你会优先考虑哪种,喜欢带团队吗?(面试官想希望了解一下候选人对业务开发部和技术管理部哪个更感兴趣)


某大型手机厂商1-2面

1.你们限流是怎么实现的?有哪些限流算法,你们的实现是基于什么算法,讲讲原理?
我们用的是RateLimit,基于令牌桶算法。原理参见:《互联网限流算法》

2.你们的接口安全性是怎么保证的,如何防止接口重放?
安全性可以使用加密通信,签名验证。接口防重参见:《基于redis的API接口幂等设计》

3.我们这边比较重视基础,我再问问你几个基础的知识。你说你了解常见的设计模式,讲讲你都了解哪些,有没有实际用过。
用过模板方法模式、责任链模式、适配器模式、策略模式,结合自己工作中用到的来阐述。

4.你说你了解常见的数据结构和算法,那讲讲你都在工作中用过哪些数据结构和算法?如何判断一个链表中是否有环?

5.你知道事务的隔离级别吗?mysql的默认隔离级别吗?数据库事务的底层原理是怎样的,如何实现事务的?

6.JAVA中的锁你了解吗?synchronized锁的状态有哪些(锁升级)?
参见:《JAVA并发编程》

7.redis部署结构了解么?你们用的哪种?分片集群是否支持pipeline?redis计算key在哪个slot是在哪里做的?hashslot比一致性hash好在哪?
主从、哨兵、集群。我们买的阿里云的redis集群。
redis集群模式也是支持pipeline的。
在redis客户端实现的。
哈希槽参见《【对线面试官】Redis分片集群》

8.动态代理有哪些实现技术?你们框架中用的是什么,你知道dubbo中用的是什么代理吗?
jdk自带的动态代理、CGLIB,Javassist,ASM。和dubbo一样,我们用的Javassist
cglib底层用的asm框架,和javaassist框架做的是差不多的事情。javaassist对开发人员更加友好。

9.你们RPC框架的线程模型是怎样的?
业务线程池和IO线程池,可以在配置文件中配置。
业务线程池可以配置到接口层(服务级别),也可以配置到方法级别。

10.你们kafka会有消息重复消费的场景吗?如何解决的?讲讲Kafka性能高的原因,讲讲零拷贝的机制?
参见:《Kafka面试题》

11.redis的其它数据结构了解么?sortedset底层数据结构为什么使用跳表?
跳表,是基于链表实现的一种类似“二分”的算法。它是在有序链表的基础上增加索引层级,上层的索引节点都加上一个down指针指向原始节点。可以快速的实现增,删,改,查操作。
插入和删除的时间复杂度就是查询元素插入位置的时间复杂度,是O(logn)。
其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。对于按照范围查询,跳表可以做到 O(logn) 的时间复杂度。先定位区间的起点,然后在第1层链表中顺序往后遍历就可以了。
而在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的方式寻找大值。

当然,Redis之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
跳表的元素插入时,只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。而平衡树的插入和删除为了保证平衡性,可能引发子树的调整,比如采取旋转操作 ,插入操作的复杂度高。
节点插入时每一个节点的层数(level)是随机出来的,元素个数n的规模很大,基于概率论,服从均匀分布,最终的表结构肯定非常接近于理想跳跃表。
redis的跳表最大层数MaxLevel = 32,在上一层的概率为1/4.

12.延迟队列
可以通过redis实现延迟队列。基于有序的集合ZSET实现。用事件触发的时间戳作为元素的score存入ZSET中。通过定时执行命令ZREMRANGEBYSCORE key start end 按指定时间戳的范围来查询当前延时队列中的延时任务,再依次对查询到的任务进行业务处理。比如可以发送到专业的消息队列中处理。

13.你们为什么采用Protocol Buffer 作为RPC的序列化框架,pb性能高的底层原理是什么?

附某公司现场编程题1例

Given n ropes of different lengths, we need to connect these ropes into one rope. We can connect only 2 ropes at a time. The cost required to connect 2 ropes is equal to sum of their lengths. The length of this connected rope is also equal to the sum of their lengths. This process is repeated until n ropes are connected into a single rope. Find the min possible cost required to connect all ropes.

Example 1:
Input: ropes = [8, 4, 6, 12]
Output: 58
Explanation: The optimal way to connect ropes is as follows

  1. Connect the ropes of length 4 and 6 (cost is 10). Ropes after connecting:
    [8, 10, 12]
  2. Connect the ropes of length 8 and 10 (cost is 18). Ropes after connecting: [18, 12]
  3. Connect the ropes of length 18 and 12 (cost is 30).
    Total cost to connect the ropes is 10 + 18 + 30 = 58

代码实现

import java.util.*;

public class Test {
    public static void main(String[] args) {
        int[] ropes = new int[]{8, 4, 6, 12};
        System.out.println(connectRopes1(ropes));
        System.out.println(connectRopes2(ropes));
    }

    /**
     * 使用暴力法,基于ArrayList自行排序
     *
     * @param ropes
     * @return
     */
    private static int connectRopes1(int[] ropes) {
        List<Integer> data = new ArrayList<>();
        for (int i = 0; i < ropes.length; i++) {
            data.add(ropes[i]);
        }
        Collections.sort(data);
        int res = 0;
        while (data.size() > 1) {
            int temp = data.get(0) + data.get(1);
            res += temp;
            data.add(temp);
            Collections.sort(data);
            data.remove(0);
            data.remove(0);
        }
        return res;
    }

    /**
     * 基于JAVA的优先级队列
     *
     * @param ropes
     * @return
     */
    private static int connectRopes2(int[] ropes) {
        Queue<Integer> queue = new PriorityQueue<Integer>();
        for (int i : ropes) {
            queue.add(i);
        }

        int res = 0;
        while (queue.size() > 1) {
            Integer temp = queue.poll() + queue.poll();
            res += temp;
            queue.offer(temp);
        }
        return res;
    }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程猿薇茑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值