2025年4、5月大厂java岗算法开放式面试题

最近辅导了几位字节后端求职同学,有些offer入职了,有些过了笔试却死在了面谈中。这些同学的情况我都清楚,明明准备的不错、属于拿的出手那种,为何区别这么大呢?复盘中发现有几位都是:经不住反问。同一道题笔试过了,却在面谈中,摆下了阵来!

以2025年5月字节java岗位的面试真题为例

请描述下如何在缓存中实现:在O(1)时间复杂度内,查询到树形结构的子节点。

审题要点:

  • O(1)时间内完成。不能是列表或数组结构。
  • 高并发情况下的缓存,容量是有限的、要有淘汰机制。最近不用的内容删除。
  • 查询子节点,1个或多个,那么查询条件就是父节点id

经过复盘

1、当面试官反问,一个线程安全的分布式key-value map不就实现了O(1)时间查询吗,为何要选择更繁琐的LRU缓存?

这部分答不好的同学都是,审题中的第2个要点没做好。高并发的情况,要确保内存安全就必须要加入内存淘汰策略,LRU算法是性价比最高的简单方案。

2、当面试官反问,你的实现中为何不单向链表?操作不是更简单一些吗。

这部分答不好的同学,审题中的2个要点没做好。要删除最近不用的,单向链表是没有办法让节点前后之间建立链接的啊。

最近一个月的复盘也发现了其它几家大厂的面试题,也越来越场景化了。**********************。

案例题的面谈要点

1、LRU算法,最少的语言说出最重要的内容:哈希表 + 双向链表

  • 哈希表:O(1) 查找
  • 双向链表:O(1) 调整顺序
  • 头部:最近使用
  • 尾部:最久未使用

操作

  • 访问:移到头部
  • 插入:放到头部,满了删尾部
  • 时间复杂度:全部 O(1)

回答的每一点,要经的起面试官反问

2、key是父结点id,value是子节点列表

案例题的笔试参考

package net.dreampo.java_share.lru_algo;

import net.dreampo.java_share.structure_algo.DoublyLinkedList;

import java.util.HashMap;

/**
 * LRU缓存实现
 * 使用双向链表维护访问顺序,HashMap提供O(1)查找
 * @param <K> 键类型
 * @param <V> 值类型
 */
public class LRUCache<K, V> {

    private HashMap<K, DoublyLinkedList<CacheEntry>.Node> cache;  // 哈希表存储键到节点的映射
    private DoublyLinkedList<CacheEntry> linkedList;              // 双向链表维护访问顺序
    private int capacity;                                          // 缓存容量

    // 缓存项,封装键值对
    private class CacheEntry {
        K key;
        V value;

        public CacheEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.linkedList = new DoublyLinkedList<>();
    }

    /**
     * 获取缓存值
     * 如果存在,将其移动到链表头部(表示最近使用)
     */
    public V get(K key) {
        DoublyLinkedList<CacheEntry>.Node node = cache.get(key);
        if (node == null) return null;

        // 将访问节点移动到链表头部(表示最近使用)
        linkedList.moveToHead(node);
        return node.getData().value;
    }

    /**
     * 设置缓存值
     * 如果已存在则更新,否则添加新节点
     * 超出容量时删除最久未使用的节点
     */
    public void put(K key, V value) {
        DoublyLinkedList<CacheEntry>.Node node = cache.get(key);

        if (node == null) { // 新节点
            CacheEntry entry = new CacheEntry(key, value);
            node = linkedList.createNode(entry);
            cache.put(key, node);
            linkedList.addToHead(node);  // 添加到链表头部

            if (linkedList.size() > capacity) { // 容量超出,删除尾部节点
                DoublyLinkedList<CacheEntry>.Node removed
                        = linkedList.removeTail();
                if (removed != null) {
                    cache.remove(removed.getData().key);
                }
            }
        } else { // 已存在节点
            node.getData().value = value;
            linkedList.moveToHead(node); // 更新访问时间
        }
    }

    /**
     * 删除指定键的缓存
     */
    public V remove(K key) {
        DoublyLinkedList<CacheEntry>.Node node = cache.get(key);
        if (node == null) return null;

        V value = node.getData().value;
        linkedList.removeNode(node);
        cache.remove(key);
        return value;
    }

    /**
     * 检查是否包含指定键
     */
    public boolean containsKey(K key) {
        return cache.containsKey(key);
    }

    /**
     * 获取当前缓存大小
     */
    public int size() {
        return linkedList.size();
    }

    /**
     * 获取缓存容量
     */
    public int capacity() {
        return capacity;
    }

    /**
     * 清空缓存
     */
    public void clear() {
        cache.clear();
        linkedList.clear();
    }

    /**
     * 判断缓存是否为空
     */
    public boolean isEmpty() {
        return linkedList.isEmpty();
    }
}

说明清楚调用方的传值

全部评论
点赞 回复 分享
发布于 06-17 15:40 广东

相关推荐

asodh:很久没有刷到过这种尸体暖暖的帖子了,你一定也是很优秀的mentor👍
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务