面试高频算法题

注: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):

  1. 类名和包: 无论什么题目,在牛客等平台上写的时候,类名务必写 public class Main,并且头部加上 import java.util.*;
  2. 规范命名: 面试官看你的代码,第一眼不是看逻辑对不对,而是看你的格式。不要用 a, b, c 命名,用 left, right, maxLen, prev
  3. 边写边说: 不要一言不发地敲键盘。写代码前先口述思路:“面试官您好,这道题我打算用双指针/哈希表来做,时间复杂度大概是...”。
posted @ 2026-03-30 01:13  Nickey103  阅读(2)  评论(0)    收藏  举报