引言:为什么需要编程题库精选实战指南?

在当今快速发展的科技行业,编程面试已成为评估开发者技能的核心标准。无论是初级工程师还是资深专家,面对算法和数据结构的挑战时,往往需要系统化的准备。信息编程题库精选实战指南正是为此而生——它不仅仅是一本题库,更是一套从基础到高阶的完整学习路径,帮助你构建坚实的算法基础,掌握高效解题技巧,并在面试中脱颖而出。

根据LeetCode、HackerRank等平台的统计,超过80%的技术面试包含算法题目,而系统准备的候选人通过率提升3倍以上。本指南将覆盖:

  • 基础篇:数组、字符串、链表等核心数据结构
  • 进阶篇:树、图、堆等复杂结构
  • 高阶篇:动态规划、回溯、贪心等高级算法
  • 实战篇:面试真题解析与优化技巧

通过本指南,你将获得:

  • 清晰的知识体系框架
  • 详细的代码实现与注释
  • 时间/空间复杂度分析
  • 面试常见陷阱与规避策略

第一部分:基础数据结构与算法(面试必考)

1.1 数组与字符串操作

数组是最基础的数据结构,但面试中常考察其边界处理和优化技巧。

经典问题:两数之和(Two Sum)

def two_sum(nums, target):
    """
    给定一个整数数组和目标值,找出两个数使它们的和为目标值。
    时间复杂度:O(n),空间复杂度:O(n)
    """
    hash_map = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in hash_map:
            return [hash_map[complement], i]
        hash_map[num] = i
    return []

# 测试用例
print(two_sum([2, 7, 11, 15], 9))  # 输出: [0, 1]
print(two_sum([3, 2, 4], 6))       # 输出: [1, 2]

关键点解析

  • 使用哈希表将查找时间从O(n)降到O(1)
  • 注意处理重复元素的情况
  • 边界条件:数组长度小于2时应返回空

字符串操作:有效的字母异位词

def is_anagram(s, t):
    """
    判断两个字符串是否为字母异位词(字符相同但顺序不同)
    """
    if len(s) != len(t):
        return False
    
    count = {}
    for char in s:
        count[char] = count.get(char, 0) + 1
    
    for char in t:
        if char not in count or count[char] == 0:
            return False
        count[char] -= 1
    
    return True

# 优化版本:使用固定大小数组(假设仅小写字母)
def is_anagram_optimized(s, t):
    if len(s) != len(t):
        return False
    
    char_count = [0] * 26
    for i in range(len(s)):
        char_count[ord(s[i]) - ord('a')] += 1
        char_count[ord(t[i]) - ord('a')] -= 1
    
    return all(count == 0 for count in char_count)

1.2 链表操作

链表是面试高频考点,重点考察指针操作和边界处理。

单链表反转

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverse_list(head):
    """
    迭代法反转链表
    时间复杂度:O(n),空间复杂度:O(1)
    """
    prev = None
    current = head
    while current:
        next_node = current.next  # 临时保存下一个节点
        current.next = prev       # 反转指针
        prev = current            # 移动prev
        current = next_node       # 移动current
    return prev

# 递归法反转链表
def reverse_list_recursive(head):
    if not head or not head.next:
        return head
    
    new_head = reverse_list_recursive(head.next)
    head.next.next = head
    head.next = None
    return new_head

# 测试代码
def print_list(head):
    result = []
    while head:
        result.append(head.val)
        head = head.next
    return result

# 构建链表 1->2->3->4->5
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)
head.next.next.next = ListNode(4)
head.next.next.next.next = ListNode(5)

reversed_head = reverse_list(head)
print(print_list(reversed_head))  # 输出: [5, 4, 3, 2, 1]

链表环检测

def has_cycle(head):
    """
    快慢指针法检测链表是否有环
    时间复杂度:O(n),空间复杂度:O(1)
    """
    if not head or not head.next:
        return False
    
    slow = head
    fast = head.next
    
    while slow != fast:
        if not fast or not fast.next:
            return False
        slow = slow.next
        fast = fast.next.next
    
    return True

1.3 栈与队列

有效括号匹配

def is_valid_parentheses(s):
    """
    判断字符串中的括号是否有效匹配
    时间复杂度:O(n),空间复杂度:O(n)
    """
    stack = []
    mapping = {')': '(', '}': '{', ']': '['}
    
    for char in s:
        if char in mapping.values():
            stack.append(char)
        elif char in mapping:
            if not stack or stack.pop() != mapping[char]:
                return False
        else:
            continue  # 忽略非括号字符
    
    return not stack

# 测试用例
print(is_valid_parentheses("()[]{}"))  # True
print(is_valid_parentheses("(]"))      # False
print(is_valid_parentheses("([)]"))    # False

用队列实现栈

from collections import deque

class MyStack:
    """
    使用两个队列实现栈的push、pop、top操作
    """
    def __init__(self):
        self.queue1 = deque()
        self.queue2 = deque()

    def push(self, x):
        self.queue1.append(x)

    def pop(self):
        while len(self.queue1) > 1:
            self.queue2.append(self.queue1.popleft())
        
        pop_element = self.queue1.popleft()
        
        # 交换队列
        self.queue1, self.queue2 = self.queue2, self.queue1
        
        return pop_element

    def top(self):
        while len(self.queue1) > 1:
            self.queue2.append(self.queue1.popleft())
        
        top_element = self.queue1[0]
        
        # 将元素移到queue2并交换
        self.queue2.append(self.queue1.popleft())
        self.queue1, self.queue2 = self.queue2, self.queue1
        
        return top_element

    def empty(self):
        return len(self.queue1) == 0

第二部分:进阶数据结构(树与图)

2.1 二叉树遍历

前序、中序、后序遍历的递归与非递归实现

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# 递归前序遍历
def preorder_traversal(root):
    result = []
    def dfs(node):
        if not node:
            return
        result.append(node.val)  # 根
        dfs(node.left)           # 左
        dfs(node.right)          # 右
    dfs(root)
    return result

# 非递归前序遍历(使用栈)
def preorder_traversal_iterative(root):
    if not root:
        return []
    
    result = []
    stack = [root]
    
    while stack:
        node = stack.pop()
        result.append(node.val)
        
        # 先右后左,保证左子树先访问
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)
    
    return result

# 中序遍历(非递归)
def inorder_traversal_iterative(root):
    result = []
    stack = []
    current = root
    
    while current or stack:
        # 一直向左走到底
        while current:
            stack.append(current)
            current = current.left
        
        # 访问节点
        current = stack.pop()
        result.append(current.val)
        
        # 转向右子树
        current = current.right
    
    return result

# 后序遍历(非递归,双栈法)
def postorder_traversal_iterative(root):
    if not root:
        return []
    
    stack1 = [root]
    stack2 = []
    
    while stack1:
        node = stack1.pop()
        stack2.append(node.val)
        
        if node.left:
            stack1.append(node.left)
        if node.right:
            stack1.append(node.right)
    
    return stack2[::-1]

层序遍历(BFS)

from collections import deque

def level_order_traversal(root):
    if not root:
        return []
    
    result = []
    queue = deque([root])
    
    while queue:
        level_size = len(queue)
        current_level = []
        
        for _ in range(level_size):
            node = queue.popleft()
            current_level.append(node.val)
            
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        
        result.append(current_level)
    
    return result

2.2 二叉搜索树(BST)

验证二叉搜索树

def is_valid_bst(root):
    """
    中序遍历应为递增序列
    """
    def inorder(node):
        if not node:
            return []
        return inorder(node.left) + [node.val] + inorder(node.right)
    
    traversal = inorder(root)
    for i in range(1, len(traversal)):
        if traversal[i] <= traversal[i-1]:
            return False
    return True

# 优化版本:使用递归上下界
def is_valid_bst_optimized(root, min_val=float('-inf'), max_val=float('inf')):
    if not root:
        return True
    
    if root.val <= min_val or root.val >= max_val:
        return False
    
    return (is_valid_bst_optimized(root.left, min_val, root.val) and
            is_valid_bst_optimized(root.right, root.val, max_val))

二叉搜索树中第K小的元素

def kth_smallest(root, k):
    """
    利用BST的中序遍历特性
    """
    stack = []
    current = root
    
    while stack or current:
        while current:
            stack.append(current)
            current = current.left
        
        current = stack.pop()
        k -= 1
        if k == 0:
            return current.val
        
        current = current.right
    
    return -1

2.3 图论基础

图的表示方法

# 邻接表表示(推荐)
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

# 邻接矩阵表示(适合稠密图)
# 0表示无边,1表示有边
adj_matrix = [
    [0, 1, 1, 0, 0, 0],
    [1, 0, 0, 1, 1, 0],
    [1, 0, 0, 0, 0, 1],
    [0, 1, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 1],
    [0, 0, 1, 0, 1, 0]
]

深度优先搜索(DFS)

def dfs_graph(graph, start, visited=None):
    if visited is None:
        visited = set()
    
    visited.add(start)
    print(start, end=' ')
    
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs_graph(graph, neighbor, visited)

# 使用DFS检测图的连通分量
def count_connected_components(graph):
    visited = set()
    count = 0
    
    def dfs(node):
        visited.add(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                dfs(neighbor)
    
    for node in graph:
        if node not in visited:
            dfs(node)
            count += 1
    
    return count

广度优先搜索(BFS)

from collections import deque

def bfs_graph(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()
        print(node, end=' ')
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

# BFS求最短路径(无权图)
def shortest_path(graph, start, end):
    if start == end:
        return [start]
    
    queue = deque([(start, [start])])
    visited = set([start])
    
    while queue:
        node, path = queue.popleft()
        
        for neighbor in graph[node]:
            if neighbor == end:
                return path + [neighbor]
            
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))
    
    return None  # 无路径

第三部分:高阶算法(面试杀手锏)

3.1 动态规划(Dynamic Programming)

动态规划是面试中最难但也是最能体现实力的部分。

爬楼梯问题(基础DP)

def climb_stairs(n):
    """
    每次可以爬1或2步,问有多少种方法爬到顶部
    状态转移方程:dp[i] = dp[i-1] + dp[i-2]
    """
    if n <= 2:
        return n
    
    dp = [0] * (n + 1)
    dp[1] = 1
    dp[2] = 2
    
    for i in range(3, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    
    return dp[n]

# 空间优化版本(只保留前两个状态)
def climb_stairs_optimized(n):
    if n <= 2:
        return n
    
    prev2 = 1  # dp[i-2]
    prev1 = 2  # dp[i-1]
    
    for i in range(3, n + 1):
        current = prev1 + prev2
        prev2 = prev1
        prev1 = current
    
    return prev1

零钱兑换(Coin Change)

def coin_change(coins, amount):
    """
    求组成金额amount所需的最少硬币数
    状态转移方程:dp[i] = min(dp[i], dp[i - coin] + 1)
    """
    # 初始化为amount+1(表示不可达)
    dp = [amount + 1] * (amount + 1)
    dp[0] = 0
    
    for i in range(1, amount + 1):
        for coin in coins:
            if i - coin >= 0:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    
    return dp[amount] if dp[amount] != amount + 1 else -1

# 测试
print(coin_change([1, 2, 5], 11))  # 输出: 3 (5+5+1)
print(coin_change([2], 3))         # 输出: -1

最长递增子序列(LIS)

def length_of_lis(nums):
    """
    时间复杂度:O(n²)
    状态:dp[i]表示以nums[i]结尾的最长递增子序列长度
    """
    if not nums:
        return 0
    
    dp = [1] * len(nums)
    
    for i in range(len(nums)):
        for j in range(i):
            if nums[i] > nums[j]:
                dp[i] = max(dp[i], dp[j] + 1)
    
    return max(dp)

# 优化版本:二分查找 O(n log n)
def length_of_lis_optimized(nums):
    import bisect
    
    tails = []
    for num in nums:
        pos = bisect.bisect_left(tails, num)
        if pos == len(tails):
            tails.append(num)
        else:
            tails[pos] = num
    
    return len(tails)

背包问题(0/1背包)

def knapsack(weights, values, capacity):
    """
    0/1背包问题:每个物品只能选或不选
    dp[i][j]表示前i个物品在容量j下的最大价值
    """
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for j in range(1, capacity + 1):
            if weights[i-1] <= j:
                # 选择或不选择当前物品
                dp[i][j] = max(
                    dp[i-1][j],  # 不选
                    dp[i-1][j - weights[i-1]] + values[i-1]  # 选
                )
            else:
                dp[i][j] = dp[i-1][j]
    
    return dp[n][capacity]

# 空间优化版本(一维数组)
def knapsack_optimized(weights, values, capacity):
    dp = [0] * (capacity + 1)
    
    for i in range(len(weights)):
        # 必须逆序遍历,避免重复选择
        for j in range(capacity, weights[i] - 1, -1):
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
    
    return dp[capacity]

# 测试
weights = [1, 3, 4, 5]
values = [1, 4, 5, 7]
capacity = 7
print(knapsack(weights, values, capacity))  # 输出: 9

3.2 回溯算法(Backtracking)

回溯是解决排列、组合、子集问题的通用框架。

全排列(Permutations)

def permute(nums):
    """
    生成所有可能的排列
    时间复杂度:O(n!)
    """
    def backtrack(path, remaining):
        if not remaining:
            result.append(path[:])
            return
        
        for i in range(len(remaining)):
            # 选择当前元素
            path.append(remaining[i])
            # 递归处理剩余元素
            backtrack(path, remaining[:i] + remaining[i+1:])
            # 撤销选择(回溯)
            path.pop()
    
    result = []
    backtrack([], nums)
    return result

# 优化版本:使用used数组避免复制列表
def permute_optimized(nums):
    def backtrack(path):
        if len(path) == len(nums):
            result.append(path[:])
            return
        
        for i in range(len(nums)):
            if not used[i]:
                used[i] = True
                path.append(nums[i])
                backtrack(path)
                path.pop()
                used[i] = False
    
    result = []
    used = [False] * len(nums)
    backtrack([])
    return result

print(permute([1, 2, 3]))
# 输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

组合问题(Combinations)

def combine(n, k):
    """
    从1..n中选择k个数字的所有组合
    """
    def backtrack(start, path):
        if len(path) == k:
            result.append(path[:])
            return
        
        # 剪枝:剩余数字不够时提前终止
        for i in range(start, n + 1):
            path.append(i)
            backtrack(i + 1, path)
            path.pop()
    
    result = []
    backtrack(1, [])
    return result

print(combine(4, 2))
# 输出: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

子集(Subsets)

def subsets(nums):
    """
    生成所有可能的子集
    """
    def backtrack(start, path):
        result.append(path[:])  # 每个路径都是一个子集
        
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    
    result = []
    backtrack(0, [])
    return result

print(subsets([1, 2, 3]))
# 输出: [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]

N皇后问题

def solve_n_queens(n):
    """
    在n×n棋盘上放置n个皇后,使其互不攻击
    """
    def backtrack(row):
        if row == n:
            result.append([''.join(board) for board in board_copy])
            return
        
        for col in range(n):
            if is_safe(row, col):
                board[row][col] = 'Q'
                board_copy[row][col] = 'Q'
                backtrack(row + 1)
                board[row][col] = '.'
                board_copy[row][col] = '.'
    
    def is_safe(row, col):
        # 检查列冲突
        for i in range(row):
            if board[i][col] == 'Q':
                return False
        
        # 检查左上对角线
        i, j = row - 1, col - 1
        while i >= 0 and j >= 0:
            if board[i][j] == 'Q':
                return False
            i -= 1
            j -= 1
        
        # 检查右上对角线
        i, j = row - 1, col + 1
        while i >= 0 and j < n:
            if board[i][j] == 'Q':
                return False
            i -= 1
            j += 1
        
        return True
    
    result = []
    board = [['.'] * n for _ in range(n)]
    board_copy = [['.'] * n for _ in range(n)]
    backtrack(0)
    return result

print(solve_n_queens(4))
# 输出: [ ['.Q..', '...Q', 'Q...', '..Q.'], ['..Q.', 'Q...', '...Q', '.Q..'] ]

3.3 贪心算法(Greedy)

贪心算法在特定问题上非常高效,但需要证明其正确性。

跳跃游戏(Jump Game)

def can_jump(nums):
    """
    判断是否能从数组第一个位置跳到最后一个位置
    贪心策略:维护当前能到达的最远位置
    """
    max_reach = 0
    for i in range(len(nums)):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + nums[i])
        if max_reach >= len(nums) - 1:
            return True
    return False

print(can_jump([2,3,1,1,4]))  # True
print(can_jump([3,2,1,0,4]))  # False

最小覆盖子串(Minimum Window Substring)

from collections import Counter

def min_window(s, t):
    """
    在s中找到包含t所有字符的最短子串
    滑动窗口 + 贪心
    """
    if not s or not t or len(s) < len(t):
        return ""
    
    t_count = Counter(t)
    required = len(t_count)
    left, right = 0, 0
    formed = 0
    window_counts = {}
    
    # 记录最短子串的长度和起始位置
    min_len = float('inf')
    result_left = 0
    
    while right < len(s):
        char = s[right]
        window_counts[char] = window_counts.get(char, 0) + 1
        
        if char in t_count and window_counts[char] == t_count[char]:
            formed += 1
        
        # 尝试收缩窗口
        while left <= right and formed == required:
            char = s[left]
            
            # 更新结果
            if right - left + 1 < min_len:
                min_len = right - left + 1
                result_left = left
            
            window_counts[char] -= 1
            if char in t_count and window_counts[char] < t_count[char]:
                formed -= 1
            
            left += 1
        
        right += 1
    
    return "" if min_len == float('inf') else s[result_left:result_left + min_len]

print(min_window("ADOBECODEBANC", "ABC"))  # 输出: "BANC"

第四部分:高级主题与优化技巧

4.1 位运算与数学技巧

位运算基础

# 判断是否是2的幂
def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0

# 计算汉明权重(1的个数)
def hamming_weight(n):
    count = 0
    while n:
        n &= n - 1  # 清除最低位的1
        count += 1
    return count

# 只出现一次的数字(其他数字出现两次)
def single_number(nums):
    result = 0
    for num in nums:
        result ^= num
    return result

快速幂算法

def my_pow(x, n):
    """
    计算x的n次幂,时间复杂度O(log n)
    """
    if n == 0:
        return 1
    
    if n < 0:
        x = 1 / x
        n = -n
    
    result = 1
    current = x
    
    while n > 0:
        if n % 2 == 1:
            result *= current
        current *= current
        n //= 2
    
    return result

print(my_pow(2, 10))  # 1024
print(my_pow(2.0, -2))  # 0.25

4.2 并查集(Union-Find)

并查集是处理动态连通性问题的利器。

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
    
    def find(self, x):
        # 路径压缩
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        
        if root_x == root_y:
            return False
        
        # 按秩合并
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1
        
        return True
    
    def connected(self, x, y):
        return self.find(x) == self.find(y)

# 使用示例:岛屿数量
def num_islands(grid):
    if not grid:
        return 0
    
    rows, cols = len(grid), len(grid[0])
    uf = UnionFind(rows * cols + 1)  # 额外一个虚拟节点
    
    # 将所有水域连接到虚拟节点
    water = rows * cols
    
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == '0':
                uf.union(i * cols + j, water)
            else:
                # 连接相邻的陆地
                if i > 0 and grid[i-1][j] == '1':
                    uf.union(i * cols + j, (i-1) * cols + j)
                if j > 0 and grid[i][j-1] == '1':
                    uf.union(i * cols + j, i * cols + j - 1)
    
    # 统计独立的陆地数量
    islands = set()
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == '1':
                islands.add(uf.find(i * cols + j))
    
    return len(islands)

4.3 滑动窗口技术

最小覆盖子串(已展示) 长度为K的子数组的最大值

from collections import deque

def max_sliding_window(nums, k):
    """
    使用双端队列维护窗口内的最大值
    时间复杂度:O(n)
    """
    if not nums:
        return []
    
    result = []
    dq = deque()  # 存储索引,保持递减顺序
    
    for i in range(len(nums)):
        # 移除超出窗口的元素
        if dq and dq[0] == i - k:
            dq.popleft()
        
        # 移除队列中比当前元素小的元素
        while dq and nums[dq[-1]] < nums[i]:
            dq.pop()
        
        dq.append(i)
        
        # 当窗口形成后,记录最大值
        if i >= k - 1:
            result.append(nums[dq[0]])
    
    return result

print(max_sliding_window([1,3,-1,-3,5,3,6,7], 3))
# 输出: [3,3,5,5,6,7]

第五部分:面试实战技巧与真题解析

5.1 面试准备策略

1. 理解题目要求(5分钟)

  • 大声读题,确认理解无误
  • 询问输入输出格式、数据范围
  • 确认边界条件

2. 设计解决方案(10分钟)

  • 先说暴力解法,分析时间复杂度
  • 思考优化方向(空间换时间、预处理、数据结构)
  • 画图辅助理解(树、图、状态转移)

3. 编写代码(15分钟)

  • 先写框架,再填细节
  • 边写边解释思路
  • 处理边界情况

4. 测试与优化(5分钟)

  • 用典型用例测试
  • 分析复杂度
  • 讨论可能的改进

5.2 高频面试真题

真题1:LRU缓存机制(设计题)

class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity: int):
        self.cache = {}
        self.capacity = capacity
        self.size = 0
        self.head = DLinkedNode()  # 虚拟头节点
        self.tail = DLinkedNode()  # 虚拟尾节点
        self.head.next = self.tail
        self.tail.prev = self.head
    
    def _add_to_head(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node):
        self._remove_node(node)
        self._add_to_head(node)
    
    def _pop_tail(self):
        res = self.tail.prev
        self._remove_node(res)
        return res
    
    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        
        node = self.cache[key]
        self._move_to_head(node)
        return node.value
    
    def put(self, key: int, value: int) -> None:
        if key not in self.cache:
            new_node = DLinkedNode(key, value)
            self.cache[key] = new_node
            self._add_to_head(new_node)
            self.size += 1
            
            if self.size > self.capacity:
                removed = self._pop_tail()
                self.cache.pop(removed.key)
                self.size -= 1
        else:
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)

# 测试
lru = LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
print(lru.get(1))  # 1
lru.put(3, 3)      # 淘汰2
print(lru.get(2))  # -1

真题2:接雨水(Trapping Rain Water)

def trap(height):
    """
    双指针法:O(n)时间,O(1)空间
    """
    if not height:
        return 0
    
    left, right = 0, len(height) - 1
    left_max, right_max = 0, 0
    water = 0
    
    while left < right:
        if height[left] < height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                water += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                water += right_max - height[right]
            right -= 1
    
    return water

print(trap([0,1,0,2,1,0,1,3,2,1,2,1]))  # 输出: 6

真题3:合并K个升序链表

import heapq

def merge_k_lists(lists):
    """
    使用最小堆合并K个升序链表
    时间复杂度:O(N log k),N是总节点数
    """
    if not lists:
        return None
    
    # 创建虚拟头节点
    dummy = ListNode(0)
    current = dummy
    
    # 初始化堆
    heap = []
    for i, node in enumerate(lists):
        if node:
            heapq.heappush(heap, (node.val, i, node))
    
    while heap:
        val, idx, node = heapq.heappop(heap)
        current.next = node
        current = current.next
        
        if node.next:
            heapq.heappush(heap, (node.next.val, idx, node.next))
    
    return dummy.next

真题4:寻找两个正序数组的中位数

def find_median_sorted_arrays(nums1, nums2):
    """
    二分查找法:O(log(min(m,n)))
    """
    if len(nums1) > len(nums2):
        nums1, nums2 = nums2, nums1
    
    m, n = len(nums1), len(nums2)
    total = m + n
    half = total // 2
    
    left, right = 0, m - 1
    
    while True:
        i = (left + right) // 2  # nums1的分割点
        j = half - i - 2         # nums2的分割点
        
        nums1_left = nums1[i] if i >= 0 else float('-inf')
        nums1_right = nums1[i + 1] if i + 1 < m else float('inf')
        nums2_left = nums2[j] if j >= 0 else float('-inf')
        nums2_right = nums2[j + 1] if j + 1 < n else float('inf')
        
        if nums1_left <= nums2_right and nums2_left <= nums1_right:
            if total % 2 == 1:
                return min(nums1_right, nums2_right)
            return (max(nums1_left, nums2_left) + min(nums1_right, nums2_right)) / 2
        elif nums1_left > nums2_right:
            right = i - 1
        else:
            left = i + 1

print(find_median_sorted_arrays([1, 3], [2]))  # 2.0
print(find_median_sorted_arrays([1, 2], [3, 4]))  # 2.5

5.3 面试常见陷阱与规避策略

陷阱1:忽略边界条件

# 错误示例:未处理空输入
def bad_example(nums):
    return nums[0]  # 空数组会崩溃

# 正确示例:防御性编程
def good_example(nums):
    if not nums:
        return None  # 或抛出异常
    return nums[0]

陷阱2:整数溢出

# 错误:计算阶乘时溢出
def factorial(n):
    result = 1
    for i in range(1, n+1):
        result *= i  # n>20时溢出
    return result

# 正确:使用模运算或大数处理
def factorial_mod(n, mod=10**9+7):
    result = 1
    for i in range(1, n+1):
        result = (result * i) % mod
    return result

陷阱3:未考虑负数情况

# 错误:二分查找边界
def binary_search_bad(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# 正确:处理重复元素和边界
def binary_search_good(arr, target):
    left, right = 0, len(arr) - 1
    # 寻找左边界
    while left < right:
        mid = (left + right) // 2
        if arr[mid] < target:
            left = mid + 1
        else:
            right = mid
    return left if arr[left] == target else -1

第六部分:学习路径与资源推荐

6.1 3个月学习计划

第1个月:基础夯实

  • 周1-2:数组、字符串、链表(每天3题)
  • 周3-4:栈、队列、哈希表(每天3题)
  • 周5-6:二叉树基础遍历(每天3题)

第2个月:进阶提升

  • 周1-2:二叉搜索树、图论(每天4题)
  • 周3-4:动态规划基础(每天4题)
  • 周5-6:回溯算法(每天4题)

第3个月:高阶冲刺

  • 周1-2:高级DP、贪心(每天5题)
  • 儿3-4:位运算、数学技巧(每天5题)
  • 周5-6:真题模拟、面试技巧(每天2套完整面试题)

6.2 推荐资源

在线平台

书籍推荐

  • 《算法导论》:理论基础
  • 《剑指Offer》:面试经典
  • 《编程珠玑》:思维训练

视频课程

  • Coursera算法专项课程
  • 牛客网面试真题解析
  • B站算法讲解视频

6.3 面试前一周 checklist

  • [ ] 复习所有错题本
  • [ ] 模拟3-5次完整面试
  • [ ] 准备自我介绍和项目介绍
  • [ ] 熟悉目标公司的技术栈
  • [ ] 调整作息,保证面试状态

结语:从理论到实践的飞跃

编程面试不仅是知识的考察,更是解决问题能力的检验。本指南提供了从基础到高阶的完整学习路径,但真正的掌握需要你:

  1. 动手实践:每个例子都要自己敲一遍
  2. 总结归纳:建立自己的知识体系
  3. 持续练习:保持手感,每天至少1题
  4. 交流讨论:加入学习小组,互相讲解

记住,算法学习没有捷径,但有方法。按照本指南的路径坚持3个月,你一定能轻松应对面试挑战,实现技能的全面提升!

最后送给大家一句话:算法不是背诵,而是理解;面试不是考试,而是交流。祝你面试顺利,拿到心仪的Offer!