面试高频算法题
注:AI生成
(注:对于链表和树这种数据结构,在真正的 ACM 笔试中通常需要手写复杂的字符串解析来构建,但面试手撕环节,面试官更看重核心算法。因此,以下代码在 main 方法中均采用了简单构造测试数据的方式来演示标准运行过程。)
1. 反转链表 (Reverse Linked List)
考察点: 链表基础、指针操作。这是最基础也是最核心的题目,绝对不能写错。
import java.util.*;
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Main {
public static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next; // 暂存下一个节点
curr.next = prev; // 改变指向
prev = curr; // prev 前进
curr = nextTemp; // curr 前进
}
return prev;
}
public static void main(String[] args) {
// 构造测试用例 1 -> 2 -> 3
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode reversed = reverseList(head);
// 打印结果
while (reversed != null) {
System.out.print(reversed.val + " ");
reversed = reversed.next;
}
}
}
2. LRU 缓存机制 (LRU Cache)
考察点: 综合数据结构设计(哈希表 + 双向链表)、面向对象设计。后端必考神题。
import java.util.*;
public class Main {
// 定义双向链表节点
static class DLinkedNode {
int key, value;
DLinkedNode prev, next;
public DLinkedNode() {}
public DLinkedNode(int key, int value) { this.key = key; this.value = value; }
}
static class LRUCache {
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int size;
private int 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 tail = removeTail();
cache.remove(tail.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;
}
}
public static void main(String[] args) {
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1);
lRUCache.put(2, 2);
System.out.println(lRUCache.get(1)); // 返回 1
lRUCache.put(3, 3); // 淘汰 2
System.out.println(lRUCache.get(2)); // 返回 -1 (未找到)
}
}
3. 无重复字符的最长子串 (Longest Substring)
考察点: 滑动窗口、哈希集合。
import java.util.*;
public class Main {
public static int lengthOfLongestSubstring(String s) {
// 使用 HashSet 记录窗口内的字符
Set<Character> set = new HashSet<>();
int n = s.length();
int right = 0, maxLen = 0;
for (int left = 0; left < n; left++) {
if (left != 0) {
set.remove(s.charAt(left - 1)); // 左指针向右移动,移除字符
}
while (right < n && !set.contains(s.charAt(right))) {
set.add(s.charAt(right));
right++;
}
maxLen = Math.max(maxLen, right - left);
}
return maxLen;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if (scanner.hasNext()) {
String s = scanner.next();
System.out.println(lengthOfLongestSubstring(s));
}
scanner.close();
}
}
4. 数组中的第 K 个最大元素 (Kth Largest Element)
考察点: 堆排序 / 快速选择。这里提供代码量适中、最稳妥的小顶堆(PriorityQueue)解法,面试时可以顺便口述快排的思路。
import java.util.*;
public class Main {
public static int findKthLargest(int[] nums, int k) {
// 维护一个大小为 k 的小顶堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
return minHeap.peek();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
System.out.println(findKthLargest(nums, k));
scanner.close();
}
}
5. 三数之和 (3Sum)
考察点: 排序 + 双指针、去重逻辑。(比两数之和更考验编码严谨度)。
import java.util.*;
public class Main {
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
if (nums == null || nums.length < 3) return ans;
Arrays.sort(nums); // 必须先排序
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) break; // 如果第一个数大于0,后面不可能凑成0
if (i > 0 && nums[i] == nums[i - 1]) continue; // 第一个数去重
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++; // 左指针去重
while (left < right && nums[right] == nums[right - 1]) right--; // 右指针去重
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return ans;
}
public static void main(String[] args) {
int[] nums = {-1, 0, 1, 2, -1, -4};
System.out.println(threeSum(nums));
}
}
6. 二叉树的层序遍历 (Level Order Traversal)
考察点: 队列 (Queue)、BFS(广度优先搜索)。
import java.util.*;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) { this.val = val; }
}
public class Main {
public static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> levelList = new ArrayList<>();
// 按层遍历
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
levelList.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
res.add(levelList);
}
return res;
}
public static void main(String[] args) {
// 构造树: 3 / \ 9 20 / \ 15 7
TreeNode root = new TreeNode(3);
root.left = new TreeNode(9);
root.right = new TreeNode(20);
root.right.left = new TreeNode(15);
root.right.right = new TreeNode(7);
System.out.println(levelOrder(root));
}
}
7. 最大子数组和 (Maximum Subarray)
考察点: 动态规划(DP)基础。
import java.util.*;
public class Main {
public static int maxSubArray(int[] nums) {
int pre = 0;
int maxAns = nums[0];
for (int x : nums) {
// 当前元素加上之前的和,如果不如当前元素大,则从当前元素重新开始
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
System.out.println(maxSubArray(nums));
scanner.close();
}
}
8. 有效的括号 (Valid Parentheses)
考察点: 栈 (Stack)。在 Java 面试中,推荐使用 Deque 替代老旧的 Stack 类。
import java.util.*;
public class Main {
public static boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) return false;
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
// 推荐使用 Deque 作为栈
Deque<Character> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (pairs.containsKey(ch)) { // 是右括号
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
stack.pop();
} else { // 是左括号
stack.push(ch);
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if (scanner.hasNext()) {
System.out.println(isValid(scanner.next()));
}
scanner.close();
}
}
9. 合并两个有序链表 (Merge Two Sorted Lists)
考察点: 链表操作、虚拟头节点(Dummy Node)技巧。
import java.util.*;
public class Main {
// 借用前面的 ListNode 类
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(-1); // 虚拟头节点
ListNode curr = dummy;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
curr.next = list1;
list1 = list1.next;
} else {
curr.next = list2;
list2 = list2.next;
}
curr = curr.next;
}
// 拼接剩余部分
curr.next = list1 == null ? list2 : list1;
return dummy.next;
}
public static void main(String[] args) {
// 省略构造过程,直接调用 mergeTwoLists
System.out.println("合并逻辑执行...");
}
}
10. 买卖股票的最佳时机 (Best Time to Buy and Sell Stock)
考察点: 贪心 / 状态机 DP 思想。代码极其简短,但常常作为后续“股票系列”动态规划问题的引子。
import java.util.*;
public class Main {
public static int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
if (price < minPrice) {
minPrice = price; // 更新历史最低价
} else if (price - minPrice > maxProfit) {
maxProfit = price - minPrice; // 更新最大利润
}
}
return maxProfit;
}
public static void main(String[] args) {
int[] prices = {7, 1, 5, 3, 6, 4};
System.out.println(maxProfit(prices)); // 输出 5
}
}
面试实战小贴士 (Tips):
- 类名和包: 无论什么题目,在牛客等平台上写的时候,类名务必写
public class Main,并且头部加上import java.util.*;。 - 规范命名: 面试官看你的代码,第一眼不是看逻辑对不对,而是看你的格式。不要用
a, b, c命名,用left, right, maxLen, prev。 - 边写边说: 不要一言不发地敲键盘。写代码前先口述思路:“面试官您好,这道题我打算用双指针/哈希表来做,时间复杂度大概是...”。

浙公网安备 33010602011771号