在Java面试笔试中,算法题始终是“分水岭”——无论是大厂初面、字节跳动等企业的“地狱级”笔试,还是中小厂的基础考察,算法题直接决定你能否进入下一轮。正如很多上岸大厂的开发者所说,“Java基础决定你能否拿到面试机会,算法能力决定你能拿到多好的offer”。
很多Java开发者精通SSM、微服务,却在算法题上栽跟头:要么思路卡顿,要么代码冗余,要么踩中笔试坑点,最终错失心仪岗位。其实Java面试笔试的算法题,并非考察复杂的算法理论,而是聚焦“基础算法+Java语法结合”,有明确的高频考点和解题模板。
本文梳理了2026年Java面试笔试最高频的6类算法题,覆盖基础岗到中高级岗,每道题均包含“题干+解题思路+Java代码模板+笔试踩坑点”,还有大厂真题延伸,帮你快速吃透、直接复用,高效备战面试笔试。
(全文干货无冗余,建议收藏,面试前1小时快速复盘,直接套用模板)
一、前言:Java算法面试笔试核心考察逻辑
先明确一个重点:Java算法面试,不考偏题、怪题,核心考察3点,也是面试官的底层判断标准:
•基础能力:掌握数组、链表、字符串、树等核心数据结构的操作,熟悉排序、查找等基础算法;
•代码能力:能用Java语法规范实现算法,兼顾可读性和效率,避免语法漏洞;
•解题思维:能快速找到最优解(时间/空间复杂度),遇到变种题能灵活调整思路(区分普通开发者和资深开发者)。
补充:不同岗位考察难度不同——基础岗侧重基础算法(排序、字符串),中高级岗侧重动态规划、贪心、树的复杂操作,大厂则会结合业务场景出算法变种题,难度更高但有规律可循。
二、高频算法题(按考察频率排序,必练)
以下题目均来自2025-2026年大厂笔试真题(阿里、腾讯、字节、华为OD等),每道题都是“出现次数多、易踩坑、可复用模板”的重点题,建议逐题吃透,熟练默写代码模板。
题型1:数组高频题——两数之和(基础必考题,通过率60%)
题干(大厂真题):给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的两个整数,并返回它们的数组下标。假设每种输入只会对应一个答案,且数组中同一个元素不能使用两次。
示例:nums = [2,7,11,15], target = 9 → 输出:[0,1]
解题思路:
核心:避免暴力枚举(时间复杂度O(n²),笔试会超时),用哈希表(HashMap)优化,时间复杂度降至O(n),空间复杂度O(n)。
1.创建HashMap,key存储数组元素,value存储元素对应的下标;
2.遍历数组,对于每个元素nums[i],计算差值complement = target - nums[i];
3.判断HashMap中是否存在complement:存在则直接返回[map.get(complement), i];不存在则将当前元素和下标存入HashMap;
4.遍历结束后,若未找到则返回空数组(按题干要求,实际不会出现此情况)。
Java代码模板(可直接复用):
import java.util.HashMap;import java.util.Map;public class TwoSum { publicint[] twoSum(int[] nums, int target) { // 注意:HashMap的key存元素,value存下标,避免重复使用同一个元素 Map<Integer, Integer> map =new HashMap<>(); for (int i = 0; i <nums.length; i++) { int complement = target -nums[i]; // 核心判断:存在则直接返回,避免二次遍历 if(map.containsKey(complement)) { return new int[]{map.get(complement), i}; } // 不存在则存入,注意顺序:先判断再存入,避免自己和自己匹配 map.put(nums[i], i); } // 按题干要求,此处可抛出异常或返回空数组 throw new IllegalArgumentException("No two sum solution"); }}
笔试踩坑点:
•误区1:暴力枚举(双重for循环),时间复杂度太高,笔试会超时,直接扣分;
•误区2:先存入元素再判断,导致同一个元素被重复使用(如nums=[3,3],target=6,会返回[0,0],错误);
•误区3:使用ArrayList存储结果,最后转数组,代码冗余(直接返回new int[]{}更简洁,符合笔试规范)。
题型2:字符串高频题——最长回文子串(中高级岗高频,通过率45%)
题干(大厂真题):给你一个字符串s,找到s中最长的回文子串。回文子串是指正读和反读都一样的字符串(如"bab"、"cbbd"),若存在多个长度相同的最长回文子串,返回任意一个即可。
示例:s = "babad" → 输出:"bab" 或 "aba";s = "cbbd" → 输出:"bb"
解题思路:
核心:回文子串分为“奇数长度”和“偶数长度”,采用“中心扩展法”(时间复杂度O(n²),空间复杂度O(1)),比动态规划更简洁,适合笔试快速实现。
1.定义两个变量,start(回文子串起始下标)和maxLen(最长回文子串长度),初始值均为0;
2.遍历字符串,对每个字符,分别以“当前字符”为中心(奇数长度)、“当前字符和下一个字符”为中心(偶数长度),进行中心扩展;
3.每次扩展后,判断当前回文子串长度是否大于maxLen,若是则更新start和maxLen;
4.遍历结束后,截取字符串s[start, start+maxLen],即为最长回文子串。
Java代码模板(可直接复用):
public class LongestPalindrome { public String longestPalindrome(String s) { // 边界判断:空字符串或长度为1的字符串,直接返回自身 if (s == null || s.length() <2) { return s; } int start = 0; int maxLen = 1; // 遍历每个字符,作为中心扩展 for (int i = 0; i <s.length(); i++) { // 奇数长度回文(中心是单个字符) int len1 =expandAroundCenter(s, i, i); // 偶数长度回文(中心是两个字符) int len2 =expandAroundCenter(s, i, i + 1); // 取两种情况的最大长度 int currLen = Math.max(len1, len2); // 更新最长回文子串的起始位置和长度 if (currLen > maxLen) { maxLen = currLen; // 计算起始下标:start = i - (currLen - 1)/2(通用公式,适配奇偶) start = i - (currLen - 1)/ 2; } } // 截取最长回文子串 return s.substring(start, start +maxLen); } // 辅助方法:中心扩展,返回当前中心的回文子串长度 private int expandAroundCenter(String s, int left, int right) { // 边界判断:left>=0、right < s.length(),且左右字符相等,继续扩展 while (left >= 0 &&right < s.length() && s.charAt(left) == s.charAt(right)) { left--; right++; } // 回文子串长度= right - left - 1(因为最后一次循环多减了1) return right - left - 1; }}
笔试踩坑点:
•误区1:忽略偶数长度回文(如"cbbd",只考虑奇数中心会漏掉"bb");
•误区2:中心扩展后,计算起始下标错误(正确公式:start = i - (currLen - 1)/2);
•误区3:使用暴力枚举(双重for循环判断所有子串是否为回文),时间复杂度O(n³),超时严重。
题型3:链表高频题——反转链表(基础必考题,通过率55%)
题干(大厂真题):给你单链表的头节点head,请你反转链表,并返回反转后的链表头节点。(要求:迭代实现,禁止使用递归,避免栈溢出)
示例:输入:1→2→3→4→5 → 输出:5→4→3→2→1
解题思路:
核心:迭代法(双指针),时间复杂度O(n),空间复杂度O(1),最适合笔试(简洁、无栈溢出风险)。
1.定义三个指针:prev(前驱节点,初始为null)、curr(当前节点,初始为head)、next(后继节点,临时存储);
2.遍历链表,每次循环中,先存储curr的后继节点next = curr.next;
3.反转curr的指针:curr.next = prev;
4.移动指针:prev = curr,curr = next;
5.遍历结束后,prev即为反转后的链表头节点(curr为null)。
Java代码模板(可直接复用):
// 先定义链表节点(笔试会给出,无需自己写,但要熟悉结构)class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; }}public class ReverseList { public ListNode reverseList(ListNode head) { // 边界判断:空链表或只有一个节点,直接返回 if (head == null || head.next ==null) { return head; } ListNode prev = null; // 前驱节点 ListNode curr = head; // 当前节点 while (curr != null) { ListNode next = curr.next; //临时存储后继节点,避免丢失 curr.next = prev; // 反转当前节点指针 // 移动指针,准备下一次循环 prev = curr; curr = next; } // 循环结束,prev是反转后的头节点 return prev; }}
笔试踩坑点:
•误区1:忘记存储后继节点next,导致链表断裂(curr.next = prev后,无法找到下一个节点);
•误区2:使用递归实现,虽然代码简洁,但链表过长时会栈溢出,笔试会扣分(面试官明确要求迭代实现);
•误区3:边界判断遗漏(空链表、单个节点,直接返回即可,无需循环)。
题型4:树高频题——二叉树的层序遍历(中高级岗高频,通过率50%)
题干(大厂真题):给你二叉树的根节点root,返回其节点值的层序遍历(即逐层地,从左到右访问所有节点),返回结果为二维列表(每一层一个子列表)。
示例:输入:root = [3,9,20,null,null,15,7] → 输出:[[3],[9,20],[15,7]]
解题思路:
核心:使用队列(Queue)实现,分层存储节点,时间复杂度O(n),空间复杂度O(n)(队列最多存储一层节点,最坏情况为叶子节点数)。
1.边界判断:若root为null,返回空列表;
2.创建队列,将root节点入队;创建二维列表result,用于存储每层节点值;
3.循环:当队列不为空时,获取当前队列的大小(即当前层的节点数);
4.创建临时列表level,遍历当前层的所有节点,将节点值存入level,同时将节点的左右子节点(非null)入队;
5.将level加入result,重复循环,直至队列为空;
6.返回result。
Java代码模板(可直接复用):
import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue;// 二叉树节点(笔试会给出,无需自己写)class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {} TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left,TreeNode right) { this.val = val; this.left = left; this.right = right; }}public class LevelOrder { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) { return result; } // 注意:笔试中用LinkedList实现Queue(ArrayList不支持poll()操作) Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); // 根节点入队 while (!queue.isEmpty()) { int levelSize = queue.size(); // 当前层的节点数 List<Integer> level =new ArrayList<>(); // 遍历当前层的所有节点 for (int i = 0; i <levelSize; i++) { TreeNode node =queue.poll(); // 出队 level.add(node.val); // 存入当前层列表 // 左右子节点入队(非null才入队) if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } result.add(level); // 当前层加入结果集 } return result; }}
笔试踩坑点:
•误区1:用ArrayList实现Queue(ArrayList没有poll()方法,会报错),正确用LinkedList;
•误区2:未获取当前层的节点数,直接遍历队列,导致无法分层(所有节点存入一个列表);
•误区3:左右子节点未判空就入队,导致队列中出现null,遍历时报空指针异常。
题型5:动态规划高频题——爬楼梯(基础必考题,通过率58%)
题干(大厂真题):假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。问有多少种不同的方法可以爬到楼顶?(n为正整数,n≤100)
示例:n=2 → 2种(1+1、2);n=3 → 3种(1+1+1、1+2、2+1);n=5 → 8种。
解题思路:
核心:动态规划(DP),避免递归超时,时间复杂度O(n),空间复杂度O(1)(优化后,无需额外数组)。
1.找规律:爬到第n阶的方法数 = 爬到第n-1阶的方法数 + 爬到第n-2阶的方法数(因为最后一步可以走1阶或2阶);
2.边界条件:n=1 → 1种,n=2 → 2种;
3.优化:无需创建DP数组,用两个变量a、b分别存储n-2、n-1阶的方法数,迭代计算n阶的方法数(a = b,b = a + b,循环n-2次)。
Java代码模板(可直接复用):
public classClimbStairs{ public int climbStairs(int n) { // 边界条件:n=1、n=2,直接返回对应方法数 if (n == 1) { return 1; } if (n == 2) { return 2; } // 优化空间:用两个变量替代DP数组 int a = 1; // 存储n-2阶的方法数(初始为n=1时的1) int b = 2; // 存储n-1阶的方法数(初始为n=2时的2) int result = 0; // 循环从3到n,计算每阶的方法数 for (int i = 3; i <= n; i++) { result = a + b; // 第i阶的方法数 = 第i-1 + 第i-2 a = b; // 更新a为n-1阶(即当前的b) b = result; // 更新b为当前阶的方法数 } return result; }}
笔试踩坑点:
•误区1:使用递归实现(f(n) = f(n-1) + f(n-2)),n较大时(如n=50)会超时(时间复杂度O(2ⁿ)),直接扣分;
•误区2:创建DP数组(int[] dp = new int[n+1]),空间复杂度O(n),虽然正确,但不够优化(面试官会期待空间O(1)的解法);
•误区3:边界条件错误(n=1时返回2,或n=2时返回1),导致后续计算全部错误。
题型6:排序高频题——快速排序(中高级岗必考题,通过率40%)
题干(大厂真题):用Java实现快速排序算法,对一个整数数组进行升序排序,要求时间复杂度尽可能优,且处理数组为空、数组长度为1的边界情况。
示例:输入:[3,1,4,1,5,9,2,6] → 输出:[1,1,2,3,4,5,6,9]
解题思路:
核心:分治法,选一个基准值(通常选数组第一个元素),将数组分为“小于基准值”和“大于基准值”两部分,再递归排序两部分,时间复杂度O(nlogn),空间复杂度O(logn)(递归栈)。
1.边界判断:数组为空或长度≤1,直接返回(无需排序);
2.选择基准值pivot(此处选数组第一个元素),定义左右指针left(左起始)、right(右起始);
3.右指针左移:找到第一个小于pivot的元素,停止;
4.左指针右移:找到第一个大于pivot的元素,停止;
5.交换左右指针指向的元素,重复步骤3-4,直至left ≥ right;
6.交换基准值和left指针指向的元素(此时基准值左边均小于它,右边均大于它);
7.递归排序基准值左边的子数组和右边的子数组,最终得到有序数组。
Java代码模板(可直接复用):
public class QuickSort { public void quickSort(int[] nums) { // 边界判断:空数组或长度≤1,直接返回 if (nums == null || nums.length<= 1) { return; } // 调用递归方法,排序整个数组(左边界0,右边界nums.length-1) sort(nums, 0, nums.length - 1); } // 递归排序子数组 private void sort(int[] nums, int left, int right) { // 递归终止条件:left >= right(子数组长度≤1) if (left >= right) { return; } int pivot = nums[left]; // 基准值(选左边界元素) int i = left; // 左指针 int j = right; // 右指针 while (i < j) { // 1. 右指针左移,找到第一个小于pivot的元素 while (i < j &&nums[j] >= pivot) { j--; } // 2. 左指针右移,找到第一个大于pivot的元素 while (i < j &&nums[i] <= pivot) { i++; } // 3. 交换左右指针指向的元素 if (i < j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } // 4. 交换基准值和i(此时i=j),基准值归位 nums[left] = nums[i]; nums[i] = pivot; // 递归排序左子数组(left到i-1)和右子数组(i+1到right) sort(nums, left, i - 1); sort(nums, i + 1, right); } // 测试方法(笔试可写可不写,方便调试) public static void main(String[] args) { QuickSort qs = new QuickSort(); int[] nums = {3,1,4,1,5,9,2,6}; qs.quickSort(nums); for (int num : nums) { System.out.print(num + ""); } }}
笔试踩坑点:
•误区1:递归终止条件遗漏(left >= right),导致栈溢出;
•误区2:左右指针移动时,未加i < j判断,导致数组越界;
•误区3:基准值归位时,交换错误(应交换nums[left]和nums[i],而非nums[j]);
•误区4:边界判断遗漏(空数组、长度为1的数组),体现不出严谨性。
三、大厂真题延伸(2026新增,必看)
以下是2026年大厂最新笔试真题变种,基于上述高频题改编,掌握核心思路后可轻松应对,避免面试时遇到变种题慌神。
1.变种题1(两数之和延伸):三数之和(字节真题)——给定一个整数数组nums,判断是否存在三个元素a、b、c,使得a+b+c=0,返回所有不重复的三元组(核心:排序+双指针,避免重复);
2.变种题2(反转链表延伸):反转链表II(阿里真题)——反转链表中从left到right的节点(核心:找到反转起始和结束节点,局部反转后拼接链表);
3.变种题3(爬楼梯延伸):爬楼梯II(华为OD真题)——每次可以爬1、2或3个台阶,求爬到第n阶的方法数(核心:动态规划,f(n) = f(n-1) + f(n-2) + f(n-3))。
四、笔试面试避坑指南(必看)
很多开发者算法思路正确,但因细节问题扣分,甚至直接挂掉,重点规避以下5个误区(结合大厂面试经验总结):
1.误区1:只记思路,不写代码 → 面试时,面试官会要求现场写代码,思路再对,代码写不出来也没用(建议每天默写1-2道题的代码模板);
2.误区2:代码不规范 → 变量命名混乱(如用a、b、c代替有意义的变量名)、缺少注释、代码冗余,体现不出开发素养,面试官会扣分;
3.误区3:忽略边界条件 → 空数组、空链表、n=0等情况,未做判断,运行时会报错,笔试直接判错;
4.误区4:追求“最复杂的解法” → 面试笔试中,“能正确运行、效率最优、代码简洁”的解法才是最好的,无需追求花哨的写法(如递归优于迭代的误区);
5.误区5:不分析时间/空间复杂度 → 面试官会追问算法的时间和空间复杂度,若答不上来,会认为你对算法理解不深入,直接扣分。
五、总结(面试快速复盘重点)
Java面试笔试的算法题,核心是“基础扎实+模板熟练+细节严谨”——无需刷遍所有算法题,重点吃透本文6类高频题,熟练默写代码模板,掌握变种题思路,就能应对80%以上的面试笔试场景。
正如上岸大厂的开发者分享,算法备考没有捷径,“每天花1小时刷题、默写模板,坚持1-2个月,就能明显看到进步”,毕竟“纸上得来终觉浅,绝知此事要躬行”,实战才是掌握算法的关键。
收藏本文,面试前重点复盘代码模板和踩坑点,避开常见误区,相信你一定能在算法题上拿到高分,顺利拿到心仪的offer!
最后,祝大家刷题顺利,面试通关,在Java开发的道路上稳步前行~