1. 手写 LRU 缓存(146)?

需求:get/put 都 O(1),容量满时淘汰最久未使用的。结构:HashMap(定位 O(1))+ 双向链表(记录访问顺序 O(1))。

核心骨架(LinkedHashMap 版一行,手写版要点)
// 手写要点(HashMap + 自建双向链表 Node)
class LRUCache {
    private final Map<Integer, Node> map = new HashMap<>();
    private final int capacity;
    private final Node head = new Node(), tail = new Node();  // 虚拟头尾

    LRUCache(int cap) { capacity = cap; head.next = tail; tail.prev = head; }

    public int get(int key) {
        Node n = map.get(key);
        if (n == null) return -1;
        moveToHead(n);
        return n.value;
    }

    public void put(int key, int value) {
        Node n = map.get(key);
        if (n != null) { n.value = value; moveToHead(n); return; }
        if (map.size() == capacity) {
            Node last = tail.prev;          // 淘汰最久未用
            removeNode(last);
            map.remove(last.key);
        }
        Node fresh = new Node(key, value);
        addToHead(fresh);
        map.put(key, fresh);
    }
    // moveToHead / addToHead / removeNode:纯指针操作,面试现场画图写
}

🎯 面试要点

  • 虚拟头尾节点消灭空指针判断——写链表题的通用技巧
  • 面试延伸:LinkedHashMap 的 removeEldestEntry(Java 基础页有);Redis 的 LRU 是抽样近似
  • 进阶:LFU(淘汰最不常用)需要频率桶 + 双向链表,两层结构

2. 接雨水(42)——双指针解法?

核心洞察:每个位置能接的水 = min(左侧最高, 右侧最高) - 当前高度(为负则 0)。

双指针优化:左右指针向中间靠,维护 leftMax/rightMax。哪边最大高度小,哪边先结算(该位置水量已确定,因为 min 被小者限制)。O(n) 时间 O(1) 空间。

双指针接雨水
public int trap(int[] h) {
    int l = 0, r = h.length - 1, lMax = 0, rMax = 0, water = 0;
    while (l < r) {
        lMax = Math.max(lMax, h[l]);
        rMax = Math.max(rMax, h[r]);
        if (lMax < rMax) {              // 左边最高小 → 结算左边
            water += lMax - h[l];
            l++;
        } else {
            water += rMax - h[r];
            r--;
        }
    }
    return water;
}

🎯 面试要点

  • 先答暴力(每位置左右扫,O(n²))→ 再答前缀最大数组(O(n) 空间)→ 最后双指针(O(1) 空间),展示递进
  • 双指针结算规则一句话:"哪边矮结算哪边"

3. 最长回文子串(5)——中心扩展?

回文串关于中心对称。中心扩展:枚举每个中心(n 个单字符中心 + n-1 个双字符中心,共 2n-1 个),向两边扩展找最长回文。O(n²) 时间 O(1) 空间。

中心扩展
public String longestPalindrome(String s) {
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expand(s, i, i);       // 奇数长度中心
        int len2 = expand(s, i, i + 1);   // 偶数长度中心
        int len = Math.max(len1, len2);
        if (len > end - start + 1) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expand(String s, int l, int r) {
    while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { l--; r++; }
    return r - l - 1;
}

🎯 面试要点

  • 进阶方案:Manacher 算法 O(n)(预处理插入 # + 维护回文半径),答出名字和思想即可
  • 回文子序列(非连续)用区间 DP:dp[i][j] = 首尾相等 ? dp[i+1][j-1]+2 : max(dp[i+1][j], dp[i][j-1])

4. 其他高频题一句话思路?

题目 思路
三数之和排序 + 固定一个 + 双指针,注意去重(跳过相同值)
合并 K 个有序链表优先队列(堆)每轮取最小头,或两两归并
有效括号栈匹配;变体"最长有效括号"用栈存下标
最大子数组和Kadane:dp[i] = max(dp[i-1] + a[i], a[i]),贪心滚动
买卖股票(一次)维护"至今最低价",每天算差价取最大
环形链表 II快慢指针相遇后,慢指针从头再走,再次相遇即环入口
无重复最长子串滑动窗口 + HashMap 记录字符最近下标
数组中的第 K 大快排 partition 剪枝(平均 O(n))或堆(O(n log k))
二叉树最近公共祖先递归:左含 p 右含 q 则当前即 LCA
字符串转换整数状态机:空格/符号/数字/结束四态,防溢出判断

🎯 面试要点

  • 刷题策略:高频 100 题反复刷,每题做到"不看答案 20 分钟写出来 + 讲清复杂度"
  • 面试现场:先确认边界(空输入、极端值)→ 讲思路 → 写代码 → 手动跑例子 → 分析复杂度

🎤 常见面试追问

  1. LRU 为什么用"哈希表 + 双向链表"而不是别的组合?——哈希表负责 O(1) 定位节点;双向链表负责 O(1) 移动/删除节点(单向链表删除需要知道前驱,做不到 O(1))。
  2. 接雨水的双指针解法,为什么"哪边矮结算哪边"?——某个位置的水量 = min(左侧最高, 右侧最高) - 自身高度;左右指针相遇时,矮的一侧其"最高值"已经确定,可以直接结算。
  3. 最长回文子串:中心扩展 vs Manacher?——中心扩展 O(n²) 好写;Manacher O(n) 用"已匹配回文右边界"加速,面试答出名字+思想即可。
  4. 三数之和为什么要先排序?去重怎么去?——排序让双指针可以从两端逼近;去重:固定数跳过相同值、双指针跳过相同值,避免重复三元组。
  5. 合并 K 个有序链表:堆解法为什么是 O(n log k)?——每次从堆取最小头 O(log k),共 n 个节点,所以 O(n log k)。

📖 名词解释(本页术语)

术语 大白话解释
LRU 缓存Least Recently Used:容量满时淘汰"最久没被用"的。就像衣柜:放不下时把最久没穿的衣服扔了。LeetCode 146 题。
双向链表每个节点有 prev 和 next 两个指针的链表,可以 O(1) 删除任意节点(知道它前驱)。
回文串正着读反着读一样的字符串,如 "abba"、"上海自来水来自海上"。
中心扩展找最长回文的方法:把每个位置(含两字符间隙)当中心,向两边扩展比较。
快慢指针两个指针速度不同地遍历(快 2 步慢 1 步),用于找环、找中点。
滑动窗口维护一个"窗口"(连续区间),右指针扩、左指针缩,找满足条件的最长/最短子数组。
Kadane 算法求最大子数组和的经典 DP:dp[i] = max(dp[i-1] + a[i], a[i]),用滚动变量 O(1) 空间。
LCA(最近公共祖先)二叉树里离两个节点最近的共同祖先节点。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。