Java算法常见面试题及答案

Posted java364100

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java算法常见面试题及答案相关的知识,希望对你有一定的参考价值。

随着疫情的好转,各大企业公司纷纷开始复工,招聘也将迎来一个高峰。Java程序员想要在这次疫情后,拿到满意的offer,就必须做好充足的准备。众所周知,算法可以说是大厂面试Java程序员的必问面试题。相信算法的重要性大家都了解,好的算法可以让性能得到万倍提升,做到毫秒级处理千万数据的程度。因此,为了提升大家在面试中的底气,本文整理了一些Java程序员算法面试题并比附上了答案,一起来看看吧!

Java算法常见面试题及答案

1、算法的时间复杂度时候是什么?

答案:算法的时间复杂度表示程序运行完成所需的总时间,它通常用大O表示法来表示。

2、合并k个有序(假设升序)数组的具体步骤是什么?

答案:将k个数组的第一个元素取出来,维护一个小顶堆;弹出堆顶元素存入结果数组中,并把该元素所在数组的下一个元素取出来压入队中;调整堆的结构,使其满足小顶堆的定义;重复前两步直到合并完成。

3、解释二分法检索如何工作?

答案:在二分法检索中,我们先确定数组的中间位置,然后将要查找的值与数组中间位置的值进行比较,若小于数组中间值,则要查找的值应位于该中间值之前,依此类推,不断缩小查找范围,直至得到最终结果。

代码拓展,二分法查找

def BinarySearch(t,x):

t.sort() #对列表进行排序,列表是有序的,是二分法的前提

low = 0;

high = len(t)-1;

while low < high:

    mid = (low+high)/2;

    if t[mid]<x:

        low=mid+1;

    elif t[mid]>x:

        high = mid-1;

    else :

        return mid

return Non

4、查找数组中出现次数超过一半的数字

答案:等价于求数组中第n/2大的数,和4中思想一样,平均时间复杂度O(n)

5、一个数组怎么输出前K大的值、时间复杂度?

答案:借助快排partition的思想,平均时间复杂度是O(n)

6、用A表示1第一列,B表示2第二列,。。。,Z表示26,AA表示27,AB表示28。。。以此类推。请写出一个函数,输入用字母表示的列号编码,输出它是第几列。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

答案:这道题的解题思路关键在于26进制转10进制。

7、输入一个正数n,输出所有和为n 连续正数序列。

答案:输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3 个连续序列1-5、4-6 和7-8。

8、输出一个整数二进制表示中1的个数。

答案:这道题的解法多样,可以右移原数判断,如果输入是负数可能陷入死循环;也可以左移1;还可以把一个整数-1后与原数做与运算会消去原数最左边的1。

9、在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

答案:这道算法面试题对大多数Java程序员来讲并不难,大致的解题思路如下,我们注意到这个二维数组的行和列都是升序的,也就是说最上面的一行和最右边的一列在整体上也是升序的,在一个排序数组上查找某个我们会很自然的想起二分法。这样我们每次都把要查找的数和当前剩下的二维数组的右上角数字比较,这样每次我们都可以排除掉一行或一列。算法的时间复杂度是O(n+m),也就是行数加列数。

10、两个排序数组A1和A2,现在想把A2插入A1中并仍保持有序。

答案:数组是个顺序表,我们往数组中插入某个数的话必须要移动当前位置后面所有的数。常规的思路是每次插入一个数并移动后面的数,这样多次插入后会导致数组中有的数被移动了多次,极大浪费了效率。我们希望每个数移动一次就到达它最终的位置,所以我们往往会反向移动数组,这样做的好处是移动当前数时后面的数已经到达了最终位置,我们移动当前数不会影响到后面的数,这样就确保了每个数只被移动一次。

11、斐波那契数列:f(0) = 0, f(1) = 1, f(n) = f(n - 1) + f(n - 2)

答案:这道算法面试题的解答方法是多样的,可以用递归,不过效率低;还可以用循环,正着推;用矩阵运算也是可以的。

12、给定一个整数序列,你可以删去其中的连续一段(可以不删),求删去后数组的最大连续子段和。

答案:最大连续子序列的变种题,从前往后遍历一遍求最大连续子序列和,然后从后往前遍历一遍求最大连续子序列和。另外,对于删去中间一段不好直接操作的话,可以先从前往后遍历,在从后往前遍历。

13、小明要在t分钟之内做l张饼,有n个锅,但只能选其中k个锅,每个锅每分钟能做vi个饼,最多能做mi个饼,问能不能做完l张饼,如果能,输出最少需要多少分钟;如果不能,输出最多能做几张饼。

答案:查询时先想一下二分。首先判断能不能做完:每个锅在t分钟内能做的饼数为min(mi,vi*t), 降序排列,前k个锅能做出来的饼>l就能;如果不能做完:直接输出前k个锅能做饼的和;如果能:二分最短时间,然后判断在mid分钟内能不能做完饼,判断方法同t分钟的情况。

14、推排序是什么?

答案:堆排序可以看成是选择排序的改进,它可以定义为基于比较的排序算法。它将其输入划分为未排序和排序的区域,通过不断消除最小元素并将其移动到排序区域来收缩未排序区域。

15、快速排序算法是什么?

答案:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列;空间复杂度为O(log2n);时间复杂度比较复杂,最好的情况是O(n),最差的情况是O(n2),所以平时说的O(nlogn),为其平均时间复杂度。

16、什么是“哈希算法”,它们用于什么?

答案:“哈希算法”是一个哈希函数,它使用任意长度的字符串,并将其减少为唯一的固定长度字符串。它用于密码有效性、消息和数据完整性以及许多其他加密系统;

17、如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。

答案:求最长公共子串是一道非常经典的动态规划题。输入两个字符串BDCABA 和ABCBDAB,字符串BCBA 和BDAB 都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。

18、如何查找链表是否有循环?

答案:要知道链表是否有循环,我们将采用两个指针的方法;如果保留两个指针,并且在处理两个节点之后增加一个指针,并且在处理每个节点之后,遇到指针指向同一个节点的情况,这只有在链表有循环时才会发生。

以上就是关于Java程序员算法面试题的全部整理,这些对应的答案大家可以在做完之后再看。如果有弄不明白的算法面试题,就需要大家好好对算法的相关知识点进行查漏补缺。最后,希望大家都能够顺利通过面试。

您可能不知道Java基础40道常见面试题及详细答案!

技术分享图片
引言
上一篇 文章我们实现了区块链的工作量证明机制(Pow),尽可能地实现了挖矿。但是距离真正的区块链应用还有很多重要的特性没有实现。今天我们来实现区块链数据的存储机制,将每次生成的区块链数据保存下来。有一点需要注意,区块链本质上是一款分布式的数据库,我们这里不实现"分布式",只聚焦于数据存储部分。

数据库选择
到目前为止,我们的实现机制中还没有区块存储这一环节,导致我们的区块每次生成之后都保存在了内存中。这样不便于我们重新使用区块链,每次都要从头开始生成区块,也不能够跟他人共享我们的区块链,因此,我们需要将其存储在磁盘上。

我们该选择哪一款数据库呢?事实上,在《比特币白皮书》中并没有明确指定使用哪一种的数据库,因此这个由开发人员自己决定。中本聪 开发的 Bitcoin Core 中使用的是LevelDB。原文 Building Blockchain in Go. Part 3: Persistence and CLI 中使用的是 BoltDB ,对Go语言支持比较好。
给大家推荐一个java内部学习群:725633148,进群找管理免费领取学习资料和视频。没有错就是免费领取!大佬小白都欢迎,大家一起学习共同进步!

但是我们这里使用的是Java来实现,BoltDB不支持Java,这里我们选用 Rocksdb

RocksDB是由Facebook数据库工程团队开发和维护的一款key-value存储引擎,比LevelDB性能更加强大,有关Rocksdb的详细介绍,请移步至官方文档:https://github.com/facebook/r... ,这里不多做介绍。
数据结构
在我们开始实现数据持久化之前,我们先要确定我们该如何去存储我们的数据。为此,我们先来看看比特币是怎么做的。

简单来讲,比特币使用了两个"buckets(桶)"来存储数据:

blocks. 描述链上所有区块的元数据.
chainstate. 存储区块链的状态,指的是当前所有的UTXO(未花费交易输出)以及一些元数据.
“在比特币的世界里既没有账户,也没有余额,只有分散到区块链里的UTXO。”
详见:《精通比特币》第二版 第06章节 —— 交易的输入与输出
此外,每个区块数据都是以单独的文件形式存储在磁盘上。这样做是出于性能的考虑:当读取某一个单独的区块数据时,不需要加载所有的区块数据到内存中来。

在 blocks 这个桶中,存储的键值对:

‘b‘ + 32-byte block hash -> block index record
区块的索引记录
‘f‘ + 4-byte file number -> file information record
文件信息记录
‘l‘ -> 4-byte file number: the last block file number used
最新的一个区块所使用的文件编码
‘R‘ -> 1-byte boolean: whether we‘re in the process of reindexing
是否处于重建索引的进程当中
‘F‘ + 1-byte flag name length + flag name string -> 1 byte boolean: various flags that can be on or off
各种可以打开或关闭的flag标志
‘t‘ + 32-byte transaction hash -> transaction index record
交易索引记录
在 chainstate 这个桶中,存储的键值对:

‘c‘ + 32-byte transaction hash -> unspent transaction output record for that transaction
某笔交易的UTXO记录
‘B‘ -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs
数据库所表示的UTXO的区块Hash(抱歉,这一点我还没弄明白……)
由于我们还没有实现交易相关的特性,因此,我们这里只使用 block 桶。另外,前面提到过的,这里我们不会实现各个区块数据各自存储在独立的文件上,而是统一存放在一个文件里面。因此,我们不要存储和文件编码相关的数据,这样一来,我们所用到的键值对就简化为:

32-byte block-hash -> Block structure (serialized)
区块数据与区块hash的键值对
‘l‘ -> the hash of the last block in a chain
最新一个区块hash的键值对
序列化
RocksDB的Key与Value只能以byte[]的形式进行存储,这里我们需要用到序列化与反序列化库 Kryo,代码如下:

package one.wangwei.blockchain.util;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

/**

  • 序列化工具类
  • @author wangwei
  • @date 2018/02/07
    */
    public class SerializeUtils {

    /**

    • 反序列化
    • @param bytes 对象对应的字节数组
    • @return
      */
      public static Object deserialize(byte[] bytes) {
      Input input = new Input(bytes);
      Object obj = new Kryo().readClassAndObject(input);
      input.close();
      return obj;
      }

    /**

    • 序列化
    • @param object 需要序列化的对象
    • @return
      */
      public static byte[] serialize(Object object) {
      Output output = new Output(4096, -1);
      new Kryo().writeClassAndObject(output, object);
      byte[] bytes = output.toBytes();
      output.close();
      return bytes;
      }
      }
      持久化
      上面已经说过,我们这里使用RocksDB,我们先写一个相关的工具类RocksDBUtils,主要的功能如下:

putLastBlockHash:保存最新一个区块的Hash值
getLastBlockHash:查询最新一个区块的Hash值
putBlock:保存区块
getBlock:查询区块
注意:BoltDB 支持 Bucket 的特性,而RocksDB 不支持,我们这里采用统一前缀的方式进行处理。
RocksDBUtils
package one.wangwei.blockchain.util;

import lombok.Getter;
import one.wangwei.blockchain.block.Block;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;

/**

  • RocksDB 工具类
  • @author wangwei
  • @date 2018/02/27
    */
    public class RocksDBUtils {

    /**

    • 区块链数据文件
      */
      private static final String DB_FILE = "blockchain.db";
      /**
    • 区块桶前缀
      */
      private static final String BLOCKS_BUCKETPREFIX = "blocks";

    private volatile static RocksDBUtils instance;

    public static RocksDBUtils getInstance() {
    if (instance == null) {
    synchronized (RocksDBUtils.class) {
    if (instance == null) {
    instance = new RocksDBUtils();
    }
    }
    }
    return instance;
    }

    @Getter
    private RocksDB rocksDB;

    private RocksDBUtils() {
    initRocksDB();
    }

    /**

    • 初始化RocksDB
      */
      private void initRocksDB() {
      try {
      rocksDB = RocksDB.open(new Options().setCreateIfMissing(true), DB_FILE);
      } catch (RocksDBException e) {
      e.printStackTrace();
      }
      }

    /**

    • 保存最新一个区块的Hash值
    • @param tipBlockHash
      */
      public void putLastBlockHash(String tipBlockHash) throws Exception {
      rocksDB.put(SerializeUtils.serialize(BLOCKS_BUCKET_PREFIX + "l"), SerializeUtils.serialize(tipBlockHash));
      }

    /**

    • 查询最新一个区块的Hash值
    • @return
      */
      public String getLastBlockHash() throws Exception {
      byte[] lastBlockHashBytes = rocksDB.get(SerializeUtils.serialize(BLOCKS_BUCKET_PREFIX + "l"));
      if (lastBlockHashBytes != null) {
      return (String) SerializeUtils.deserialize(lastBlockHashBytes);
      }
      return "";
      }

    /**

    • 保存区块
    • @param block
      */
      public void putBlock(Block block) throws Exception {
      byte[] key = SerializeUtils.serialize(BLOCKS_BUCKET_PREFIX + block.getHash());
      rocksDB.put(key, SerializeUtils.serialize(block));
      }

    /**

    • 查询区块
    • @param blockHash
    • @return
      */
      public Block getBlock(String blockHash) throws Exception {
      byte[] key = SerializeUtils.serialize(BLOCKS_BUCKET_PREFIX + blockHash);
      return (Block) SerializeUtils.deserialize(rocksDB.get(key));
      }

}
创建区块链
现在我们来优化 Blockchain.newBlockchain 接口的代码逻辑,改为如下逻辑:

代码如下:

/**

  • <p> 创建区块链 </p>
  • @return
    */
    public static Blockchain newBlockchain() throws Exception {
    String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
    if (StringUtils.isBlank(lastBlockHash)) {
    Block genesisBlock = Block.newGenesisBlock();
    lastBlockHash = genesisBlock.getHash();
    RocksDBUtils.getInstance().putBlock(genesisBlock);
    RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash);
    }
    return new Blockchain(lastBlockHash);
    }
    修改 Blockchain 的数据结构,只记录最新一个区块链的Hash值

public class Blockchain {

@Getter
private String lastBlockHash;

private Blockchain(String lastBlockHash) {
    this.lastBlockHash = lastBlockHash;
}

}
每次挖矿完成后,我们也需要将最新的区块信息保存下来,并且更新最新区块链Hash值:

/**

  • <p> 添加区块 </p>
  • @param data
    */
    public void addBlock(String data) throws Exception {
    String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
    if (StringUtils.isBlank(lastBlockHash)) {
    throw new Exception("Fail to add block into blockchain ! ");
    }
    this.addBlock(Block.newBlock(lastBlockHash, data));
    }

/**

  • <p> 添加区块 </p>
  • @param block
    */
    public void addBlock(Block block) throws Exception {
    RocksDBUtils.getInstance().putLastBlockHash(block.getHash());
    RocksDBUtils.getInstance().putBlock(block);
    this.lastBlockHash = block.getHash();
    }
    到此,存储部分的功能就实现完毕,我们还缺少一个功能:

检索区块链
现在,我们所有的区块都保存到了数据库,因此,我们能够重新打开已有的区块链并且向其添加新的区块。但这也导致我们再也无法打印出区块链中所有区块的信息,因为,我们没有将区块存储在数组当中。让我们来修复这个瑕疵!

我们在Blockchain中创建一个内部类 BlockchainIterator ,作为区块链的迭代器,通过区块之前的hash连接来依次迭代输出区块信息,代码如下:

public class Blockchain {

....

/**
 * 区块链迭代器
 */
public class BlockchainIterator {

    private String currentBlockHash;

    public BlockchainIterator(String currentBlockHash) {
        this.currentBlockHash = currentBlockHash;
    }

    /**
     * 是否有下一个区块
     *
     * @return
     */
    public boolean hashNext() throws Exception {
        if (StringUtils.isBlank(currentBlockHash)) {
            return false;
        }
        Block lastBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
        if (lastBlock == null) {
            return false;
        }
        // 创世区块直接放行
        if (lastBlock.getPrevBlockHash().length() == 0) {
            return true;
        }
        return RocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash()) != null;
    }

    /**
     * 返回区块
     *
     * @return
     */
    public Block next() throws Exception {
        Block currentBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
        if (currentBlock != null) {
            this.currentBlockHash = currentBlock.getPrevBlockHash();
            return currentBlock;
        }
        return null;
    }
}   

....    

}
测试
/**

  • 测试
  • @author wangwei
  • @date 2018/02/05
    */
    public class BlockchainTest {

    public static void main(String[] args) {
    try {
    Blockchain blockchain = Blockchain.newBlockchain();

        blockchain.addBlock("Send 1.0 BTC to wangwei");
        blockchain.addBlock("Send 2.5 more BTC to wangwei");
        blockchain.addBlock("Send 3.5 more BTC to wangwei");
    
        for (Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator(); iterator.hashNext(); ) {
            Block block = iterator.next();
    
            if (block != null) {
                boolean validate = ProofOfWork.newProofOfWork(block).validate();
                System.out.println(block.toString() + ", validate = " + validate);
            }
        }
    
    } catch (Exception e) {
        e.printStackTrace();
    }

    }
    }

/输出/

Block{hash=‘0000012f87a0510dd0ee7048a6bd52db3002bae7d661126dc28287bd6c23189a‘, prevBlockHash=‘0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf‘, data=‘Send 3.5 more BTC to wangwei‘, timeStamp=1519724875, nonce=369110}, validate = true
Block{hash=‘0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf‘, prevBlockHash=‘00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79‘, data=‘Send 2.5 more BTC to wangwei‘, timeStamp=1519724872, nonce=896348}, validate = true
Block{hash=‘00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79‘, prevBlockHash=‘0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703‘, data=‘Send 1.0 BTC to wangwei‘, timeStamp=1519724869, nonce=673955}, validate = true
Block{hash=‘0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703‘, prevBlockHash=‘‘, data=‘Genesis Block‘, timeStamp=1519724866, nonce=840247}, validate = true
命令行界面
CLI 部分的内容,这里不做详细介绍,具体可以去查看文末的Github源码链接。大致步骤如下:

配置

添加pom.xml配置

<project>

...

<dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.4</version>
</dependency>

...

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
                <mainClass>one.wangwei.blockchain.cli.Main</mainClass>
            </manifest>
        </archive>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <!-- this is used for inheritance merges -->
            <phase>package</phase>
            <!-- 指定在打包节点执行jar包合并操作 -->
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

...

</project>
项目工程打包

$ mvn clean && mvn package
执行命令

打印帮助信息

$ java -jar blockchain-java-jar-with-dependencies.jar -h

添加区块

$ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 1.5 BTC to wangwei"
$ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 2.5 BTC to wangwei"
$ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 3.5 BTC to wangwei"

打印区块链

$ java -jar blockchain-java-jar-with-dependencies.jar -print
总结
本篇我们实现了区块链的存储功能,接下来我们将实现地址、交易、钱包这一些列的功能。
给大家推荐一个java内部学习群:725633148,进群找管理免费领取学习资料和视频。没有错就是免费领取!大佬小白都欢迎,大家一起学习共同进步!

以上是关于Java算法常见面试题及答案的主要内容,如果未能解决你的问题,请参考以下文章

java常见面试题及答案

2020最新Java常见面试题及答案

java常见面试题及答案

java常见面试题及部分答案

面试准备——selenium常见面试题及答案(java)

Java常见面试题及答案汇总