数组与链表高频面试题精讲 ⭐⭐⭐

🎯 面试核心

数组和链表是最基础的数据结构,也是面试的必考题。掌握这两种结构的特性、常见操作和高频题目,是进阶算法的基础。


一、数组高频面试题

1. 两数之和(LeetCode 1)

问题描述

给定一个整数数组 nums 和一个整数 target,找出数组中两个数之和等于 target 的下标,返回这两个下标。

输入:nums = [2, 7, 11, 15], target = 9
输出:[0, 1]
解释:nums[0] + nums[1] = 2 + 7 = 9

思路分析

Java 代码(哈希表)

public int[] twoSum(int[] nums, int target) {
    // 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);
    }
    
    return new int[]{};  // 无解
}

复杂度分析


2. 三数之和(LeetCode 15)

问题描述

给定一个数组 nums,找出所有满足 nums[i] + nums[j] + nums[k] = 0 的三元组,且不重复。

输入:nums = [-1, 0, 1, 2, -1, -4]
输出:[[-1, -1, 2], [-1, 0, 1]]

思路分析

Java 代码

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    
    if (nums == null || nums.length < 3) {
        return result;
    }
    
    // 第一步:排序
    Arrays.sort(nums);
    
    for (int i = 0; i < nums.length - 2; i++) {
        // 优化:如果当前数 > 0,后续不可能有和为 0 的三元组
        if (nums[i] > 0) break;
        
        // 去重:跳过重复的第一个数
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        
        // 双指针找另外两个数
        int left = i + 1;
        int right = nums.length - 1;
        int target = -nums[i];
        
        while (left < right) {
            int sum = nums[left] + nums[right];
            
            if (sum == target) {
                result.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 < target) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}

复杂度分析


3. 最长连续序列(LeetCode 128)

问题描述

给定一个未排序的整数数组,找出最长连续元素序列的长度。

输入:nums = [100, 4, 200, 1, 3, 2]
输出:4
解释:最长连续序列是 [1, 2, 3, 4],长度为 4

思路分析

Java 代码

public int longestConsecutive(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    
    // 转换为 HashSet,O(1) 查询
    Set<Integer> numSet = new HashSet<>();
    for (int num : nums) {
        numSet.add(num);
    }
    
    int maxLength = 0;
    
    for (int num : numSet) {
        // 只从序列的起点开始计数
        if (!numSet.contains(num - 1)) {
            int currentNum = num;
            int currentLength = 1;
            
            // 向后计数
            while (numSet.contains(currentNum + 1)) {
                currentNum++;
                currentLength++;
            }
            
            maxLength = Math.max(maxLength, currentLength);
        }
    }
    
    return maxLength;
}

复杂度分析


4. 合并区间(LeetCode 56)

问题描述

给定一个区间列表,合并所有重叠的区间。

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]

思路分析

Java 代码

public int[][] merge(int[][] intervals) {
    if (intervals == null || intervals.length == 0) {
        return new int[0][0];
    }
    
    // 按起点排序
    Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
    
    List<int[]> merged = new ArrayList<>();
    int[] current = intervals[0];
    
    for (int i = 1; i < intervals.length; i++) {
        if (intervals[i][0] <= current[1]) {
            // 重叠,合并
            current[1] = Math.max(current[1], intervals[i][1]);
        } else {
            // 不重叠,保存当前区间,开始新区间
            merged.add(current);
            current = intervals[i];
        }
    }
    
    // 别忘了最后一个区间
    merged.add(current);
    
    return merged.toArray(new int[0][0]);
}

复杂度分析


二、链表高频面试题

5. 反转链表(LeetCode 206)

问题描述

反转一个单链表。

输入:1 -> 2 -> 3 -> 4 -> 5
输出:5 -> 4 -> 3 -> 2 -> 1

思路分析

Java 代码(迭代法)

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    
    while (curr != null) {
        // 保存下一个节点
        ListNode next = curr.next;
        
        // 反转指针
        curr.next = prev;
        
        // 前移指针
        prev = curr;
        curr = next;
    }
    
    return prev;  // 新的头节点
}

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

Java 代码(递归法)

public ListNode reverseListRecursive(ListNode head) {
    // 递归终止条件
    if (head == null || head.next == null) {
        return head;
    }
    
    // 递归反转后续链表
    ListNode newHead = reverseListRecursive(head.next);
    
    // 反转当前节点
    head.next.next = head;
    head.next = null;
    
    return newHead;
}

复杂度分析


6. 合并有序链表(LeetCode 21)

问题描述

合并两个有序链表为一个有序链表。

输入:l1 = 1 -> 2 -> 4, l2 = 1 -> 3 -> 4
输出:1 -> 1 -> 2 -> 3 -> 4 -> 4

思路分析

Java 代码

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    // 虚拟头节点
    ListNode dummy = new ListNode(0);
    ListNode curr = dummy;
    
    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            curr.next = l1;
            l1 = l1.next;
        } else {
            curr.next = l2;
            l2 = l2.next;
        }
        curr = curr.next;
    }
    
    // 连接剩余部分
    curr.next = (l1 != null) ? l1 : l2;
    
    return dummy.next;
}

复杂度分析


7. 链表环检测(LeetCode 141 & 142)

问题描述

检测链表中是否存在环,如果存在,找出环的入口。

思路分析

Java 代码

// 检测是否有环
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    
    ListNode slow = head;
    ListNode fast = head;
    
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        
        if (slow == fast) {
            return true;  // 有环
        }
    }
    
    return false;  // 无环
}

// 找环的入口
public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) {
        return null;
    }
    
    ListNode slow = head;
    ListNode fast = head;
    
    // 第一步:检测是否有环
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        
        if (slow == fast) {
            // 有环,进入第二步
            break;
        }
    }
    
    // 如果没有环
    if (fast == null || fast.next == null) {
        return null;
    }
    
    // 第二步:找环入口
    ListNode ptr1 = head;
    ListNode ptr2 = slow;
    
    while (ptr1 != ptr2) {
        ptr1 = ptr1.next;
        ptr2 = ptr2.next;
    }
    
    return ptr1;  // 环入口
}

复杂度分析


8. 相交链表(LeetCode 160)

问题描述

给定两个链表,找出它们的相交节点。

链表 A:a1 -> a2 -> c1 -> c2 -> c3
链表 B:b1 -> b2 -> b3 -> c1 -> c2 -> c3
相交节点:c1

思路分析

Java 代码

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }
    
    ListNode pA = headA;
    ListNode pB = headB;
    
    // 两个指针同时遍历
    while (pA != pB) {
        // pA 到达末尾后,转向 headB
        pA = (pA == null) ? headB : pA.next;
        
        // pB 到达末尾后,转向 headA
        pB = (pB == null) ? headA : pB.next;
    }
    
    return pA;  // 相交节点或 null
}

复杂度分析


三、高频面试题总结

题目 难度 关键点 时间复杂度
两数之和 哈希表 O(N)
三数之和 ⭐⭐ 排序 + 双指针 + 去重 O(N²)
最长连续序列 ⭐⭐ 哈希集合 + 起点判断 O(N)
合并区间 ⭐⭐ 排序 + 贪心 O(N log N)
反转链表 指针反转 O(N)
合并有序链表 虚拟头节点 O(N + M)
链表环检测 ⭐⭐ Floyd 算法 O(N)
相交链表 ⭐⭐ 双指针 O(N + M)

四、面试建议

  1. 数组:掌握哈希表、双指针、排序等基础技巧
  2. 链表:熟练使用虚拟头节点、快慢指针等技巧
  3. 代码规范:边界条件、空指针检查、变量命名
  4. 时间复杂度:优先选择 O(N) 或 O(N log N) 的解法
  5. 多做练习:LeetCode 上反复练习,形成肌肉记忆