Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)

Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)

📝 摘要:基于2026春招/实习面经,精选10+道后端高频核心题,涵盖双指针、动态规划、回溯剪枝、滑动窗口、环形链表、LRU缓存、环形打家劫舍、SQL原地操作、B+树索引、KMP字符串匹配、HTTP演进与TCP调优。每道题附带完整可运行Java代码、面试官追问、进阶思考。末尾补充系统设计REACT推导法,助你打通技术与业务的底层逻辑。


📚 系列导航


一、算法与大模型前沿篇

1. 链表相交节点检测

  • 考察点:双指针技巧、空间复杂度优化
  • 解题思路 :使用双指针,分别从两链表头出发,到达末尾后跳转至另一链表头部继续遍历。若相交必在交点相遇;若不相交最终均为null。时间复杂度 O(m+n),空间复杂度 O(1)
  • 进阶思考:如果允许修改原链表结构,有没有更快的方法?如果链表存在环,这道题该怎么解?主动提及这些边界条件能极大提升你的工程素养。
java 复制代码
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode p = headA, q = headB;
    while (p != q) {
        // 走到末尾则跳转到另一条链表的头部
        p = (p == null) ? headB : p.next;
        q = (q == null) ? headA : q.next;
    }
    return p; // 若不相交,最终 p 和 q 均为 null
}

💬 你在写双指针时,有没有遇到过因为空指针判断顺序而导致的bug?欢迎评论区分享。


2. 动态规划状态定义误区(乘积最大子数组)

  • 考察点:DP状态定义准确性、负数场景应对
  • 解题思路 :以"乘积最大子数组"为例,需同时维护以i结尾的最大值和最小值,因为负数会让最小值瞬间变为最大值。错误的状态定义 :"前i个元素的最大乘积";正确的状态定义 :"以第i个元素结尾的子数组的最值"。
  • 进阶思考:该模型可直接映射到金融风控中的"连续异常交易最大回撤与收益乘积"检测。
java 复制代码
public int maxProduct(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    // 状态定义:以当前位置结尾的最大/最小乘积
    int maxEndingHere = nums[0], minEndingHere = nums[0], res = nums[0];
    for (int i = 1; i < nums.length; i++) {
        int preMax = maxEndingHere, preMin = minEndingHere;
        maxEndingHere = Math.max(nums[i], Math.max(preMax * nums[i], preMin * nums[i]));
        minEndingHere = Math.min(nums[i], Math.min(preMax * nums[i], preMin * nums[i]));
        res = Math.max(res, maxEndingHere);
    }
    return res;
}

3. 回溯法剪枝优化(组合问题)

  • 考察点:搜索空间压缩、剪枝条件推导
  • 解题思路 :在组合枚举中,当剩余可选元素不足以填满目标数量时,提前终止搜索。剪枝条件为 i <= n - (k - path.size()) + 1
  • 进阶思考:在大模型的束搜索(Beam Search)和推荐系统的候选集生成中,类似的剪枝策略被广泛应用于减少解码空间。
java 复制代码
public List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> res = new ArrayList<>();
    backtrack(1, new ArrayList<>(), n, k, res);
    return res;
}

private void backtrack(int start, List<Integer> path, int n, int k, List<List<Integer>> res) {
    if (path.size() == k) {
        res.add(new ArrayList<>(path));
        return;
    }
    for (int i = start; i <= n - (k - path.size()) + 1; i++) {
        path.add(i);
        backtrack(i + 1, path, n, k, res);
        path.remove(path.size() - 1);
    }
}

4. 滑动窗口求最大连续1的个数(含k次翻转)

  • 考察点:窗口收缩条件控制
  • 解题思路 :维护窗口内0的个数不超过k,右指针不断扩展窗口,当0的个数超标时收缩左指针。利用前缀和思想统计0的个数,时间复杂度O(n)
  • 进阶思考:滑动窗口思想可无缝迁移到大模型RAG系统中的长文本分块处理,以及API限流的滑动日志窗口统计。
java 复制代码
public int longestOnes(int[] nums, int k) {
    int left = 0, maxLen = 0, zeroCount = 0;
    for (int right = 0; right < nums.length; right++) {
        if (nums[right] == 0) zeroCount++;
        while (zeroCount > k) {
            if (nums[left++] == 0) zeroCount--;
        }
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}

5. 环形链表检测与入环点定位

  • 考察点:快慢指针、数学推导
  • 解题思路:快指针每次走两步,慢指针走一步,相遇则存在环。定位入环点时,将快指针重置为头节点,两指针同步移动,再次相遇点即为入环点。
  • 进阶思考:该算法在JVM内存泄漏检测(循环引用)和分布式系统死锁检测中有重要应用。
java 复制代码
public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) return null;
    ListNode slow = head, fast = head;
    boolean hasCycle = false;
    
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) { hasCycle = true; break; }
    }
    if (!hasCycle) return null;
    
    ListNode ptr = head;
    while (ptr != slow) {
        ptr = ptr.next;
        slow = slow.next;
    }
    return ptr;
}

💬 你在推导入环点公式时,有没有更直观的记忆方法?欢迎评论区交流。


二、系统设计与高并发实战篇

6. LRU缓存机制实现

  • 考察点:哈希表与双向链表结合、O(1)操作
  • 解题思路HashMap存储key到节点的映射,双向链表维护访问顺序。get操作将节点移至头部,put操作在容量满时淘汰尾部节点。
  • 面试官追问
    • 多线程环境下如何保证线程安全? → 使用 ReentrantLock 分段锁,或 ConcurrentHashMap + CAS操作。
    • 分布式场景下如何解决缓存穿透、击穿、雪崩? → 布隆过滤器、互斥锁/逻辑过期、TTL加随机值与多级缓存兜底。
java 复制代码
class LRUCache {
    class DLinkedNode {
        int key, value;
        DLinkedNode prev, next;
        DLinkedNode() {}
        DLinkedNode(int key, int value) { this.key = key; this.value = value; }
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    private int size, capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) return -1;
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            DLinkedNode newNode = new DLinkedNode(key, value);
            cache.put(key, newNode);
            addToHead(newNode);
            size++;
            if (size > capacity) {
                DLinkedNode removed = removeTail();
                cache.remove(removed.key);
                size--;
            }
        } else {
            node.value = value;
            moveToHead(node);
        }
    }
    
    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}

7. 打家劫舍II(环形房屋)

  • 考察点:动态规划变体、环形问题拆解
  • 解题思路:将环形问题拆分为两个线性问题:不偷第一间或不偷最后一间,分别求解后取最大值,巧妙避免首尾冲突。
java 复制代码
public int rob(int[] nums) {
    int n = nums.length;
    if (n == 1) return nums[0];
    // 比较 [0, n-2] 与 [1, n-1] 两个区间
    return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));
}

private int robRange(int[] nums, int start, int end) {
    int prev = 0, curr = 0;
    for (int i = start; i <= end; i++) {
        int temp = Math.max(curr, prev + nums[i]);
        prev = curr;
        curr = temp;
    }
    return curr;
}

三、数据库与存储篇

8. 原地操作优化(SQL技巧)

  • 考察点:减少临时表、利用CASE WHEN与变量
  • 解题思路 :使用 CASE WHEN 实现值交换、使用用户变量实现行号及累加,避免显式创建临时表,降低I/O开销。
  • 进阶思考:在数据迁移和宽表构建中,原地更新比临时表+重命名更节省空间和锁等待时间。

示例:交换性别字段值

UPDATE students SET sex = CASE WHEN sex = 'm' THEN 'f' ELSE 'm' END;


9. B+树索引底层与优化

  • 考察点:B+树结构、聚集索引与覆盖索引
  • 解题思路:B+树所有数据存于叶子节点,叶子通过双向链表连接。聚集索引的叶子存储整行数据,非聚集索引叶子存储主键值。覆盖索引可直接从索引返回数据,避免回表。
  • 面试官追问:为什么B+树比B树更适合做数据库索引? → B+树范围查询效率更高,且非叶子节点不存数据使得树更矮、IO更少。

四、网络协议深水区

10. KMP字符串匹配算法

  • 考察点:模式串预处理、主串指针不回退
  • 解题思路 :预处理模式串生成next数组(最长公共前后缀表),发生不匹配时根据next滑动模式串,时间复杂度O(m+n)
  • 进阶思考:KMP被广泛应用于海量日志关键字检索、敏感词过滤系统以及编辑器内的"查找"功能。
java 复制代码
public int strStr(String haystack, String needle) {
    if (needle.isEmpty()) return 0;
    int[] next = getNext(needle);
    int j = 0;
    for (int i = 0; i < haystack.length(); i++) {
        while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
            j = next[j - 1];
        }
        if (haystack.charAt(i) == needle.charAt(j)) j++;
        if (j == needle.length()) return i - j + 1;
    }
    return -1;
}

private int[] getNext(String s) {
    int[] next = new int[s.length()];
    int j = 0;
    for (int i = 1; i < s.length(); i++) {
        while (j > 0 && s.charAt(i) != s.charAt(j)) j = next[j - 1];
        if (s.charAt(i) == s.charAt(j)) j++;
        next[i] = j;
    }
    return next;
}

11. HTTP全版本演进与TCP调优

  • 考察点:协议底层原理、内核级调优
  • 面试官追问
    • HTTP/3基于QUIC解决了HTTP/2的哪些痛点? → 解决了TCP层的队头阻塞问题,并在握手上实现了0-RTT。
    • 线上服务器出现大量TIME_WAIT该如何排查与调优? → 使用 ss -tan state time-wait | wc -l 查看数量;若为短连接过多导致,可调整内核参数 net.ipv4.tcp_tw_reuse=1,并考虑连接池化。

五、高频后端系统设计概要(REACT推导)

为了让"高并发架构设计"名副其实,这里用 REACT 推导流程 拆解一道常见设计题:

题目:设计一个短链接系统

步骤 内容
R (Requirement) 支持长链转短链、短链跳转长链;短链永不过期;日均1亿+PV。
E (Estimate) 每月需存储约30亿条映射;单表过大需分库分表。
A (Architecture) 采用分布式ID生成(雪花算法)得到唯一ID,转为62进制作为短链码。
C (Component) Web服务层 → 短链服务 → 分布式缓存(Redis) → 分库分表(MySQL)。
T (Tradeoff) 使用302临时重定向以保留原始请求数据;热点短链做本地缓存。

通过这种结构化的推理过程,能向面试官展示扎实的系统设计功底。


总结与备考建议

领域 高频考点 推荐掌握程度
算法 双指针、动态规划、回溯剪枝 熟练手写代码,主动分析时空复杂度
系统设计 LRU缓存、短链接/热搜架构 理解REACT推导流程,掌握高并发组件
数据库 原地操作、B+树索引原理 掌握核心SQL优化与底层存储机制
网络 KMP、HTTP演进、TCP调优 深入理解协议细节与线上排障方案

核心建议 :面对技术问题,请养成使用 "是什么 → 为什么 → 怎么实现 → 有什么问题 → 怎么优化" 的逻辑闭环进行阐述。在刷题策略上,优先保障中等难度题目的正确率,重点突破动态规划状态定义、双指针技巧及LRU缓存的独立手写能力。


📌 Java实战可运行完整代码

java 复制代码
import java.util.*;

public class InterviewMaster2026 {

    // ---------- 链表节点定义 ----------
    static class ListNode {
        int val;
        ListNode next;
        ListNode(int x) { val = x; }
    }

    // ---------- 1. 链表相交节点检测 ----------
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p = headA, q = headB;
        while (p != q) {
            p = (p == null) ? headB : p.next;
            q = (q == null) ? headA : q.next;
        }
        return p;
    }

    // ---------- 2. 乘积最大子数组 ----------
    public int maxProduct(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int maxEndingHere = nums[0], minEndingHere = nums[0], res = nums[0];
        for (int i = 1; i < nums.length; i++) {
            int preMax = maxEndingHere, preMin = minEndingHere;
            maxEndingHere = Math.max(nums[i], Math.max(preMax * nums[i], preMin * nums[i]));
            minEndingHere = Math.min(nums[i], Math.min(preMax * nums[i], preMin * nums[i]));
            res = Math.max(res, maxEndingHere);
        }
        return res;
    }

    // ---------- 3. 组合(回溯+剪枝) ----------
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        backtrack(1, new ArrayList<>(), n, k, res);
        return res;
    }

    private void backtrack(int start, List<Integer> path, int n, int k, List<List<Integer>> res) {
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i <= n - (k - path.size()) + 1; i++) {
            path.add(i);
            backtrack(i + 1, path, n, k, res);
            path.remove(path.size() - 1);
        }
    }

    // ---------- 4. 滑动窗口:最大连续1(含k次翻转) ----------
    public int longestOnes(int[] nums, int k) {
        int left = 0, maxLen = 0, zeroCount = 0;
        for (int right = 0; right < nums.length; right++) {
            if (nums[right] == 0) zeroCount++;
            while (zeroCount > k) {
                if (nums[left++] == 0) zeroCount--;
            }
            maxLen = Math.max(maxLen, right - left + 1);
        }
        return maxLen;
    }

    // ---------- 5. 环形链表检测与入环点定位 ----------
    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null) return null;
        ListNode slow = head, fast = head;
        boolean hasCycle = false;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) { hasCycle = true; break; }
        }
        if (!hasCycle) return null;
        ListNode ptr = head;
        while (ptr != slow) {
            ptr = ptr.next;
            slow = slow.next;
        }
        return ptr;
    }

    // ---------- 6. LRU 缓存实现 ----------
    static class LRUCache {
        class DLinkedNode {
            int key, value;
            DLinkedNode prev, next;
            DLinkedNode() {}
            DLinkedNode(int k, int v) { key = k; value = v; }
        }

        private Map<Integer, DLinkedNode> cache = new HashMap<>();
        private int size, capacity;
        private DLinkedNode head, tail;

        public LRUCache(int capacity) {
            this.size = 0;
            this.capacity = capacity;
            head = new DLinkedNode();
            tail = new DLinkedNode();
            head.next = tail;
            tail.prev = head;
        }

        public int get(int key) {
            DLinkedNode node = cache.get(key);
            if (node == null) return -1;
            moveToHead(node);
            return node.value;
        }

        public void put(int key, int value) {
            DLinkedNode node = cache.get(key);
            if (node == null) {
                DLinkedNode newNode = new DLinkedNode(key, value);
                cache.put(key, newNode);
                addToHead(newNode);
                size++;
                if (size > capacity) {
                    DLinkedNode removed = removeTail();
                    cache.remove(removed.key);
                    size--;
                }
            } else {
                node.value = value;
                moveToHead(node);
            }
        }

        private void addToHead(DLinkedNode node) {
            node.prev = head;
            node.next = head.next;
            head.next.prev = node;
            head.next = node;
        }

        private void removeNode(DLinkedNode node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }

        private void moveToHead(DLinkedNode node) {
            removeNode(node);
            addToHead(node);
        }

        private DLinkedNode removeTail() {
            DLinkedNode res = tail.prev;
            removeNode(res);
            return res;
        }
    }

    // ---------- 7. 打家劫舍II(环形房屋) ----------
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];
        return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));
    }

    private int robRange(int[] nums, int start, int end) {
        int prev = 0, curr = 0;
        for (int i = start; i <= end; i++) {
            int temp = Math.max(curr, prev + nums[i]);
            prev = curr;
            curr = temp;
        }
        return curr;
    }

    // ---------- 8. KMP 字符串匹配 ----------
    public int strStr(String haystack, String needle) {
        if (needle.isEmpty()) return 0;
        int[] next = getNext(needle);
        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = next[j - 1];
            }
            if (haystack.charAt(i) == needle.charAt(j)) j++;
            if (j == needle.length()) return i - j + 1;
        }
        return -1;
    }

    private int[] getNext(String s) {
        int[] next = new int[s.length()];
        int j = 0;
        for (int i = 1; i < s.length(); i++) {
            while (j > 0 && s.charAt(i) != s.charAt(j)) j = next[j - 1];
            if (s.charAt(i) == s.charAt(j)) j++;
            next[i] = j;
        }
        return next;
    }

    // ---------- 测试主函数 ----------
    public static void main(String[] args) {
        InterviewMaster2026 app = new InterviewMaster2026();

        // 1. 相交链表测试
        ListNode a1 = new ListNode(1); ListNode a2 = new ListNode(2);
        ListNode b1 = new ListNode(3); ListNode b2 = new ListNode(4);
        ListNode c1 = new ListNode(5); ListNode c2 = new ListNode(6);
        a1.next = a2; a2.next = c1; c1.next = c2;
        b1.next = b2; b2.next = c1; // 在 c1 相交
        ListNode intersect = app.getIntersectionNode(a1, b1);
        System.out.println("相交节点值: " + (intersect != null ? intersect.val : "null"));

        // 2. 乘积最大子数组
        int[] nums = {2, 3, -2, 4};
        System.out.println("最大乘积: " + app.maxProduct(nums)); // 6

        // 3. 组合测试
        List<List<Integer>> comb = app.combine(4, 2);
        System.out.println("组合 C(4,2): " + comb);

        // 4. 滑动窗口测试
        int[] arr = {1,1,0,1,1,1};
        System.out.println("最长连续1 (k=0): " + app.longestOnes(arr, 0)); // 3
        int[] arr2 = {0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1};
        System.out.println("最长连续1 (k=3): " + app.longestOnes(arr2, 3)); // 10

        // 5. 环形链表测试
        ListNode head = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        head.next = node2; node2.next = node3; node3.next = node2; // 环入口为 node2
        ListNode cycleNode = app.detectCycle(head);
        System.out.println("入环点值: " + (cycleNode != null ? cycleNode.val : "null"));

        // 6. LRU 缓存测试
        LRUCache lru = new LRUCache(2);
        lru.put(1, 1);
        lru.put(2, 2);
        System.out.println("LRU get(1): " + lru.get(1)); // 1
        lru.put(3, 3); // 淘汰 key=2
        System.out.println("LRU get(2): " + lru.get(2)); // -1
        lru.put(4, 4); // 淘汰 key=1
        System.out.println("LRU get(1): " + lru.get(1)); // -1
        System.out.println("LRU get(3): " + lru.get(3)); // 3
        System.out.println("LRU get(4): " + lru.get(4)); // 4

        // 7. 打家劫舍II
        int[] houses = {2, 3, 2};
        System.out.println("打家劫舍II max: " + app.rob(houses)); // 3
        int[] houses2 = {1, 2, 3, 1};
        System.out.println("打家劫舍II max: " + app.rob(houses2)); // 4

        // 8. KMP 测试
        String str = "hello", pattern = "ll";
        System.out.println("KMP 匹配位置: " + app.strStr(str, pattern)); // 2
        String str2 = "aaaaa", pattern2 = "bba";
        System.out.println("KMP 匹配位置: " + app.strStr(str2, pattern2)); // -1
    }
}

运行说明:

  • 将以上代码保存为 InterviewMaster2026.java
  • 编译:javac InterviewMaster2026.java
  • 运行:java InterviewMaster2026

输出预览:

复制代码
相交节点值: 5
最大乘积: 6
组合 C(4,2): [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
最长连续1 (k=0): 3
最长连续1 (k=3): 10
入环点值: 2
LRU get(1): 1
LRU get(2): -1
LRU get(1): -1
LRU get(3): 3
LRU get(4): 4
打家劫舍II max: 3
打家劫舍II max: 4
KMP 匹配位置: 2
KMP 匹配位置: -1

📚 系列导航

相关推荐
AOwhisky1 小时前
Redis 学习笔记(第三期):持久化与主从复制
运维·数据库·redis·笔记·学习·云计算
李白的天不白1 小时前
数据库连接报错问题
数据库
一条泥憨鱼2 小时前
【Redis】数据类型和常用命令
java·数据库·redis·后端·缓存
专注VB编程开发20年2 小时前
c#Modbus上位机开发-一次读10个地址和100个地址速度一样
网络·网络协议·tcp/ip
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【78】沙箱(Sandbox)
java·人工智能·spring
程序员二叉2 小时前
【Java】 异常高频面试题精讲 | 易错点+对比总结
java·开发语言·面试
爱喝水的鱼丶2 小时前
SAP-ABAP:SAP视图开发入门:四类标准视图的适用场景与创建步骤详解
服务器·数据库·性能优化·sap·abap
周航宇JoeZhou2 小时前
JB3-9-SpringAI(二)
java·ai·agent·多智能体·调度·智能体·观察
好家伙VCC2 小时前
Web Components主题热切换方案揭秘
java·前端