大厂面试专项讲解 - 字节/阿里/腾讯真题¶
重要性:⭐⭐⭐⭐⭐ 目标:大厂面试准备 内容:真题分类 + 解题思路 + 面试技巧
📚 目录¶
字节跳动面试真题¶
面试特点¶
- 算法难度:⭐⭐⭐⭐⭐(偏难)
- 考察重点:动态规划、图算法、系统设计
- 编程语言:主要使用Python/Go/Java
- 时间限制:通常45-60分钟
题目1:LRU缓存机制 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 146
难度:中等
出现频率:⭐⭐⭐⭐⭐(字节超高频)
问题描述: 请你设计并实现一个满足 LRU (最近最少使用) 缓存约束的数据结构。实现 LRUCache 类: - LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 - int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 - void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value;如果不存在,则向缓存中插入该组 key-value。如果插入操作导致关键字数量超过 capacity,则应该逐出最久未使用的关键字。
解法:哈希表 + 双向链表(推荐)⭐¶
class DLinkedNode:
"""双向链表节点"""
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
"""
LRU缓存
时间:O(1) get和put
空间:O(capacity)
"""
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {} # 哈希表:key -> 节点
# 创建伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
def _remove_node(self, node):
"""移除节点"""
node.prev.next = node.next
node.next.prev = node.prev
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 _move_to_head(self, node):
"""移动节点到头部"""
self._remove_node(node)
self._add_to_head(node)
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 in self.cache:
# 更新值,移动到头部
node = self.cache[key]
node.value = value
self._move_to_head(node)
else:
# 创建新节点
node = DLinkedNode(key, value)
self.cache[key] = node
self._add_to_head(node)
# 检查容量
if len(self.cache) > self.capacity:
# 移除尾部节点(最久未使用)
removed = self.tail.prev
self._remove_node(removed)
del self.cache[removed.key]
执行过程演示:
LRUCache lRUCache = new LRUCache(2);
put(1, 1): cache = {1:1}
链表: head <-> (1,1) <-> tail
put(2, 2): cache = {1:1, 2:2}
链表: head <-> (2,2) <-> (1,1) <-> tail
get(1): 返回 1
cache = {1:1, 2:2}
链表: head <-> (1,1) <-> (2,2) <-> tail
(1被访问,移到头部)
put(3, 3): 容量超限,移除(2,2)
cache = {1:1, 3:3}
链表: head <-> (3,3) <-> (1,1) <-> tail
get(2): 返回 -1(未找到)
put(4, 4): 容量超限,移除(1,1)
cache = {3:3, 4:4}
链表: head <-> (4,4) <-> (3,3) <-> tail
复杂度分析: - 时间:O(1),get和put都是常数时间 - 空间:O(capacity),哈希表和链表都存储capacity个元素
面试要点: 1. 为什么用双向链表? - O(1)时间删除节点(需要前驱节点)
- 为什么用哈希表?
-
O(1)时间查找节点
-
为什么需要伪头尾节点?
- 简化边界条件处理
题目2:四数之和 ⭐⭐⭐⭐¶
题目链接:LeetCode 18
难度:中等
出现频率:⭐⭐⭐⭐
问题描述: 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
解法:排序 + 双指针 ⭐¶
def four_sum(nums, target):
"""
四数之和
时间:O(n³)
空间:O(1)
"""
nums.sort()
n = len(nums)
result = []
for i in range(n - 3):
# 跳过重复
if i > 0 and nums[i] == nums[i - 1]:
continue
for j in range(i + 1, n - 2):
# 跳过重复
if j > i + 1 and nums[j] == nums[j - 1]:
continue
# 双指针
left, right = j + 1, n - 1
while left < right:
current_sum = nums[i] + nums[j] + nums[left] + nums[right]
if current_sum == target:
result.append([nums[i], nums[j], nums[left], nums[right]])
# 跳过重复
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif current_sum < target:
left += 1
else:
right -= 1
return result
优化技巧: 1. 提前终止:如果最小的4个数之和 > target,可以提前返回 2. 剪枝:如果当前数 + 最大的3个数 < target,跳过当前数
题目3:接雨水 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 42
难度:困难
出现频率:⭐⭐⭐⭐⭐(字节必考)
问题描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解法1:双指针(推荐)⭐¶
def trap(height):
"""
接雨水(双指针)
时间:O(n)
空间:O(1)
"""
if not height:
return 0
left, right = 0, len(height) - 1
left_max, right_max = height[left], height[right]
water = 0
while left < right:
if left_max < right_max:
left += 1
left_max = max(left_max, height[left])
water += left_max - height[left]
else:
right -= 1
right_max = max(right_max, height[right])
water += right_max - height[right]
return water
执行过程演示:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
图示:
█
█ █ █
█ █ █ █ █ █
█ █ █ █ █ █ █ █
0 1 0 2 1 0 1 3 2 1 2 1
初始:left=0, right=11, left_max=0, right_max=1
第1次迭代:
- left_max(0) < right_max(1)
- left++ → left=1
- left_max = max(0, 1) = 1
- water += 1 - 1 = 0
第2次迭代:
- left_max(1) >= right_max(1)
- right-- → right=10
- right_max = max(1, 2) = 2
- water += 2 - 2 = 0
...(继续这个过程)
最终结果:6
核心思想: - 水量取决于左右两边较小的最大值 - 维护left_max和right_max - 总是移动较小的一侧
解法2:动态规划¶
def trap_dp(height):
"""
接雨水(动态规划)
时间:O(n)
空间:O(n)
"""
if not height:
return 0
n = len(height)
# left_max[i] = height[0:i]的最大值
left_max = [0] * n
left_max[0] = height[0]
for i in range(1, n):
left_max[i] = max(left_max[i - 1], height[i])
# right_max[i] = height[i:n]的最大值
right_max = [0] * n
right_max[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
# 计算水量
water = 0
for i in range(n):
water += min(left_max[i], right_max[i]) - height[i]
return water
题目4:最长有效括号 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 32
难度:困难
出现频率:⭐⭐⭐⭐⭐
问题描述: 给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(正确连续)括号子串的长度。
解法:栈(推荐)⭐¶
def longest_valid_parentheses(s):
"""
最长有效括号(栈)
时间:O(n)
空间:O(n)
"""
max_length = 0
stack = [-1] # 初始化:-1作为起始位置
for i, char in enumerate(s):
if char == '(':
stack.append(i)
else:
# 弹出栈顶
stack.pop()
if not stack:
# 栈空,压入当前索引作为新的起始位置
stack.append(i)
else:
# 计算有效长度
current_length = i - stack[-1]
max_length = max(max_length, current_length)
return max_length
执行过程演示:
输入:s = ")()())"
初始:stack = [-1], max_length = 0
i=0, char=')':
- stack.pop() → stack = []
- 栈空,stack.append(0)
- stack = [0]
i=1, char='(':
- stack.append(1)
- stack = [0, 1]
i=2, char=')':
- stack.pop() → stack = [0]
- 栈不空
- length = 2 - 0 = 2
- max_length = 2
i=3, char='(':
- stack.append(3)
- stack = [0, 3]
i=4, char=')':
- stack.pop() → stack = [0]
- 栈不空
- length = 4 - 0 = 4
- max_length = 4
i=5, char=')':
- stack.pop() → stack = []
- 栈空,stack.append(5)
- stack = [5]
结果:4
子串:"()()"
题目5:滑动窗口最大值 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 239
难度:困难
出现频率:⭐⭐⭐⭐⭐
问题描述: 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
解法:单调队列(推荐)⭐¶
from collections import deque
def max_sliding_window(nums, k):
"""
滑动窗口最大值(单调队列)
时间:O(n)
空间:O(k)
"""
if not nums or k <= 0:
return []
# 单调递减队列:存储索引
deque_idx = deque()
result = []
for i, num in enumerate(nums):
# 移除超出窗口的元素
while deque_idx and deque_idx[0] <= i - k:
deque_idx.popleft()
# 移除比当前元素小的元素(单调递减)
while deque_idx and nums[deque_idx[-1]] < num: # 负索引:从末尾倒数访问元素
deque_idx.pop()
# 添加当前元素索引
deque_idx.append(i)
# 记录窗口最大值
if i >= k - 1:
result.append(nums[deque_idx[0]])
return result
执行过程演示:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
i=0, num=1:
- deque = [0]
- i < k-1,不记录
i=1, num=3:
- 3 > 1,移除0
- deque = [1]
- i < k-1,不记录
i=2, num=-1:
- deque = [1, 2]
- i >= k-1,记录nums[1]=3
- result = [3]
i=3, num=-3:
- 移除索引0(无)
- deque = [1, 2, 3]
- 记录nums[1]=3
- result = [3, 3]
i=4, num=5:
- 移除索引1(超出窗口)
- 5 > -3, -1, 3,全部移除
- deque = [4]
- 记录nums[4]=5
- result = [3, 3, 5]
i=5, num=3:
- deque = [4, 5]
- 记录nums[4]=5
- result = [3, 3, 5, 5]
i=6, num=6:
- 6 > 5, 3,全部移除
- deque = [6]
- 记录nums[6]=6
- result = [3, 3, 5, 5, 6]
i=7, num=7:
- 7 > 6,移除
- deque = [7]
- 记录nums[7]=7
- result = [3, 3, 5, 5, 6, 7]
结果:[3, 3, 5, 5, 6, 7]
阿里巴巴面试真题¶
面试特点¶
- 算法难度:⭐⭐⭐⭐
- 考察重点:数据库、分布式系统、设计模式
- 编程语言:主要使用Java
- 时间限制:通常45-60分钟
题目6:反转链表II ⭐⭐⭐⭐¶
题目链接:LeetCode 92
难度:中等
出现频率:⭐⭐⭐⭐
问题描述: 给你单链表的头指针 head 和两个整数 left 和 right,其中 left <= right。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表。
解法:迭代(推荐)⭐¶
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_between(head, left, right):
"""
反转链表II
时间:O(n)
空间:O(1)
"""
# 创建虚拟头节点
dummy = ListNode(0)
dummy.next = head
# 第1步:找到left前一个节点
prev = dummy
for _ in range(left - 1):
prev = prev.next
# 第2步:反转从left到right的节点
current = prev.next
reverse_prev = None
for _ in range(right - left + 1):
next_node = current.next
current.next = reverse_prev
reverse_prev = current
current = next_node
# 第3步:连接反转后的链表
prev.next.next = current
prev.next = reverse_prev
return dummy.next
执行过程演示:
输入:head = [1,2,3,4,5], left = 2, right = 4
初始链表:1 -> 2 -> 3 -> 4 -> 5
第1步:找到left前一个节点
- prev指向节点1
第2步:反转[2,3,4]
- current = 2
- 第1次:2.next = None, reverse_prev = 2
- 第2次:3.next = 2, reverse_prev = 3
- 第3次:4.next = 3->2, reverse_prev = 4
第3步:连接
- prev.next.next = current (2.next = 5)
- prev.next = reverse_prev (1.next = 4)
结果:1 -> 4 -> 3 -> 2 -> 5 ✓
题目7:二叉树的中序遍历 ⭐⭐⭐¶
题目链接:LeetCode 94
难度:简单
出现频率:⭐⭐⭐⭐
问题描述: 给定一个二叉树的根节点 root,返回它的中序遍历。
解法1:递归¶
def inorder_traversal(root):
"""
中序遍历(递归)
时间:O(n)
空间:O(h)
"""
result = []
def inorder(node):
if not node:
return
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return result
解法2:迭代(推荐)⭐¶
def inorder_traversal_iterative(root):
"""
中序遍历(迭代)
时间:O(n)
空间:O(h)
"""
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
题目8: Pow(x, n) ⭐⭐⭐⭐¶
题目链接:LeetCode 50
难度:中等
出现频率:⭐⭐⭐⭐
问题描述: 实现 pow(x, n),即计算 x 的 n 次幂函数。
解法:快速幂(推荐)⭐¶
def my_pow(x, n):
"""
Pow(x, n)(快速幂)
时间:O(log n)
空间:O(1)
"""
def fast_pow(x, n):
if n == 0:
return 1.0
half = fast_pow(x, n // 2)
if n % 2 == 0:
return half * half
else:
return half * half * x
if n < 0:
x = 1 / x
n = -n
return fast_pow(x, n)
数学原理:
执行过程演示:
计算:2^10
fast_pow(2, 10):
- half = fast_pow(2, 5)
fast_pow(2, 5):
- half = fast_pow(2, 2)
fast_pow(2, 2):
- half = fast_pow(2, 1)
fast_pow(2, 1):
- half = fast_pow(2, 0) = 1
- 1是奇数,返回 1*1*2 = 2
- 2是偶数,返回 2*2 = 4
- 5是奇数,返回 4*4*2 = 32
- 10是偶数,返回 32*32 = 1024
结果:1024 ✓
题目9:最长公共前缀 ⭐⭐⭐¶
题目链接:LeetCode 14
难度:简单
出现频率:⭐⭐⭐⭐
问题描述: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
解法:垂直扫描(推荐)⭐¶
def longest_common_prefix(strs):
"""
最长公共前缀(垂直扫描)
时间:O(m * n),m是最短字符串长度,n是字符串数量
空间:O(1)
"""
if not strs:
return ""
# 找到最短字符串
min_str = min(strs, key=len)
for i, char in enumerate(min_str):
# 检查所有字符串的第i个字符
for s in strs:
if s[i] != char:
return min_str[:i]
return min_str
题目10:括号生成 ⭐⭐⭐⭐¶
题目链接:LeetCode 22
难度:中等
出现频率:⭐⭐⭐⭐⭐
问题描述: 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。
解法:回溯(推荐)⭐¶
def generate_parenthesis(n):
"""
括号生成(回溯)
时间:O(4^n / sqrt(n))
空间:O(n)
"""
result = []
def backtrack(current, left, right):
"""
current: 当前生成的字符串
left: 左括号数量
right: 右括号数量
"""
# 终止条件:生成了2n个字符
if len(current) == 2 * n:
result.append(current)
return
# 添加左括号(如果左括号数量 < n)
if left < n:
backtrack(current + '(', left + 1, right)
# 添加右括号(如果右括号数量 < 左括号数量)
if right < left:
backtrack(current + ')', left, right + 1)
backtrack('', 0, 0)
return result
执行过程演示:
输入:n = 2
递归树:
""
/ \
"(1,0)" (不能添加")")
/ \
"(2,0)" "(1,1)"
/ \
"(2,1)" "(2,1)" → "()()"
↓ ↓
"(2,2)" → "(())" "(2,2)" → "()()"
结果:["(())", "()()"]
腾讯面试真题¶
面试特点¶
- 算法难度:⭐⭐⭐⭐
- 考察重点:网络编程、操作系统、数据结构
- 编程语言:C++/Java/Go
- 时间限制:通常45-60分钟
题目11:全排列 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 46
难度:中等
出现频率:⭐⭐⭐⭐⭐
问题描述: 给定一个不含重复数字的数组 nums,返回其所有可能的全排列。你可以按任意顺序返回答案。
解法1:回溯 + used数组(推荐)⭐¶
def permute(nums):
"""
全排列(回溯)
时间:O(n! * n)
空间:O(n)
"""
result = []
n = len(nums)
used = [False] * n
def backtrack(current):
# 终止条件:生成了n个数字
if len(current) == n:
result.append(current[:])
return
for i in range(n):
if not used[i]:
# 选择
used[i] = True
current.append(nums[i])
# 递归
backtrack(current)
# 撤销选择
current.pop()
used[i] = False
backtrack([])
return result
解法2:交换法(优化)⭐⭐¶
def permute_swap(nums):
"""
全排列(交换法)
时间:O(n! * n)
空间:O(1)(不包括输出)
"""
result = []
def backtrack(start):
# 终止条件
if start == len(nums):
result.append(nums[:])
return
for i in range(start, len(nums)):
# 交换
nums[start], nums[i] = nums[i], nums[start]
# 递归
backtrack(start + 1)
# 撤销交换
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return result
执行过程演示:
输入:nums = [1, 2, 3]
递归树:
[1,2,3]
/ | \
swap(0,0) swap(0,1) swap(0,2)
| | |
[1,2,3] [2,1,3] [3,2,1]
/ \ / \ / \
[1,2,3] [1,3,2] [2,1,3] [2,3,1] [3,2,1] [3,1,2]
结果:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
题目12:子集 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 78
难度:中等
出现频率:⭐⭐⭐⭐⭐
问题描述: 给你一个整数数组 nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。
解法:回溯(推荐)⭐¶
def subsets(nums):
"""
子集(回溯)
时间:O(2^n * n)
空间:O(n)
"""
result = []
def backtrack(start, current):
# 每次递归都记录当前子集
result.append(current[:])
# 从start开始,避免重复
for i in range(start, len(nums)):
# 选择nums[i]
current.append(nums[i])
# 递归(从i+1开始,避免重复使用)
backtrack(i + 1, current)
# 撤销选择
current.pop()
backtrack(0, [])
return result
执行过程演示:
输入:nums = [1, 2, 3]
递归树:
[]
/ | \
[1] [2] [3]
/ \ |
[1,2] [1,3] [2,3]
/
[1,2,3]
结果:[[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]]
题目13:组合 ⭐⭐⭐⭐¶
题目链接:LeetCode 77
难度:中等
出现频率:⭐⭐⭐⭐⭐
问题描述: 给定两个整数 n 和 k,返回 1...n 中所有可能的 k 个数的组合。
解法:回溯(推荐)⭐¶
def combine(n, k):
"""
组合(回溯)
时间:O(C(n,k) * k)
空间:O(k)
"""
result = []
def backtrack(start, current):
# 终止条件:找到k个数
if len(current) == k:
result.append(current[:]) # 切片操作:[start:end:step]提取子序列
return
# 从start到n
# 剪枝优化:i最多到 n - (k - len(current)) + 1
for i in range(start, n - (k - len(current)) + 2):
# 选择i
current.append(i)
# 递归(从i+1开始,保证递增顺序)
backtrack(i + 1, current)
# 撤销选择
current.pop()
backtrack(1, [])
return result
剪枝优化图解:
n=4, k=2
不剪枝:
i的范围:[1, 4]
- i=1: [1,2], [1,3], [1,4]
- i=2: [2,3], [2,4]
- i=3: [3,4]
- i=4: [4,?](无效,无法凑够2个)
剪枝后:
i的范围:[1, 3](n - k + 2 = 4 - 2 + 2 = 4,但range(1, 4)=[1,2,3])
- i=1: [1,2], [1,3], [1,4]
- i=2: [2,3], [2,4]
- i=3: [3,4]
(跳过了i=4的无效情况)
题目14:单词搜索 ⭐⭐⭐⭐¶
题目链接:LeetCode 79
难度:中等
出现频率:⭐⭐⭐⭐
问题描述: 给定一个 m x n 二维字符网格 board 和一个字符串单词 word。如果 word 存在于网格中,返回 true;否则,返回 false。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中"相邻"单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
解法:回溯 + DFS(推荐)⭐¶
def exist(board, word):
"""
单词搜索
时间:O(m * n * 4^L),L是word长度
空间:O(L)
"""
if not board or not board[0]:
return False
m, n = len(board), len(board[0])
def dfs(i, j, index):
"""
i, j: 当前位置
index: word的索引
"""
# 终止条件:找到完整单词
if index == len(word):
return True
# 边界检查 + 字符匹配
if i < 0 or i >= m or j < 0 or j >= n:
return False
if board[i][j] != word[index]:
return False
# 标记已访问
temp = board[i][j]
board[i][j] = '#'
# 向四个方向搜索
found = (dfs(i + 1, j, index + 1) or
dfs(i - 1, j, index + 1) or
dfs(i, j + 1, index + 1) or
dfs(i, j - 1, index + 1))
# 恢复
board[i][j] = temp
return found
# 从每个位置开始搜索
for i in range(m):
for j in range(n):
if dfs(i, j, 0):
return True
return False
题目15:岛屿数量 ⭐⭐⭐⭐⭐¶
题目链接:LeetCode 200
难度:中等
出现频率:⭐⭐⭐⭐⭐(腾讯必考)
问题描述: 给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
解法:DFS(推荐)⭐¶
def num_islands(grid):
"""
岛屿数量(DFS)
时间:O(m * n)
空间:O(m * n)
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
count = 0
def dfs(i, j):
"""标记相连的陆地"""
# 边界检查
if i < 0 or i >= m or j < 0 or j >= n:
return
if grid[i][j] != '1':
return
# 标记为已访问
grid[i][j] = '2'
# 向四个方向扩展
dfs(i + 1, j)
dfs(i - 1, j)
dfs(i, j + 1)
dfs(i, j - 1)
# 遍历整个网格
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
# 发现新岛屿
count += 1
# 标记整个岛屿
dfs(i, j)
return count
执行过程演示:
输入:
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
遍历:
(0,0) = '1':发现岛屿1,标记(0,0),(0,1),(1,0),(1,1)
grid = [
["2","2","0","0","0"],
["2","2","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
(2,2) = '1':发现岛屿2,标记(2,2)
grid = [
["2","2","0","0","0"],
["2","2","0","0","0"],
["0","0","2","0","0"],
["0","0","0","1","1"]
]
(3,3) = '1':发现岛屿3,标记(3,3),(3,4)
grid = [
["2","2","0","0","0"],
["2","2","0","0","0"],
["0","0","2","0","0"],
["0","0","0","2","2"]
]
结果:3 ✓
面试技巧与策略¶
1. 算法面试流程¶
第1步:理解题意(2-3分钟)
- 明确输入输出
- 确认边界条件
- 举例说明
第2步:分析思路(3-5分钟)
- 暴力解法
- 优化思路
- 时间空间复杂度
第3步:编写代码(15-20分钟)
- 先写核心逻辑
- 处理边界情况
- 添加必要注释
第4步:测试验证(3-5分钟)
- 正常情况
- 边界情况
- 特殊情况
第5步:优化讨论(2-3分钟)
- 是否有更优解
- 其他解法
2. 沟通技巧¶
✅ 好的沟通¶
- "让我先理解一下题目..."
- "我的思路是..."
- "时间复杂度是O(n),因为..."
- "让我用个例子验证一下..."
❌ 不好的沟通¶
- (直接开始写代码,不说话)
- (写完代码不解释)
- "我忘了..."
- (遇到问题长时间沉默)
3. 代码风格¶
✅ 好的风格¶
def two_sum(nums, target):
"""
两数之和
时间:O(n)
空间:O(n)
"""
# 创建哈希表
num_to_index = {}
for i, num in enumerate(nums):
complement = target - num
# 检查补数
if complement in num_to_index:
return [num_to_index[complement], i]
num_to_index[num] = i
return []
❌ 不好的风格¶
def f(a,b):
d={}
for i,x in enumerate(a): # enumerate同时获取索引和值
y=b-x
if y in d:return[d[y],i]
d[x]=i
return[]
4. 常见陷阱¶
陷阱1:边界条件¶
# ❌ 忘记处理空数组
def max_sub_array(nums):
return max(nums) # 空数组会报错
# ✅ 正确处理
def max_sub_array(nums):
if not nums:
return 0
return max(nums)
陷阱2:整数溢出¶
# ❌ 可能溢出
def multiply(a, b):
return a * b # 大数可能溢出
# ✅ 使用模运算
def multiply(a, b, mod=10**9 + 7):
return (a * b) % mod
陷阱3:索引越界¶
# ❌ 可能越界
def get_next(arr, i):
return arr[i + 1] # 最后一个元素会越界
# ✅ 边界检查
def get_next(arr, i):
if i + 1 < len(arr):
return arr[i + 1]
return None
高频考点总结¶
数据结构高频考点¶
1. 数组¶
- ✅ 双指针(两数之和、合并区间)
- ✅ 滑动窗口(最小覆盖子串)
- ✅ 前缀和(和为K的子数组)
2. 链表¶
- ✅ 快慢指针(环形链表、链表中点)
- ✅ 虚拟头节点(简化操作)
- ✅ 反转链表(迭代、递归)
3. 树¶
- ✅ 递归遍历(前、中、后序)
- ✅ 迭代遍历(栈、队列)
- ✅ BST性质(左<根<右)
4. 图¶
- ✅ BFS(最短路径)
- ✅ DFS(连通性)
- ✅ 拓扑排序(课程表)
算法高频考点¶
1. 动态规划¶
- ✅ 线性DP(爬楼梯、打家劫舍)
- ✅ 背包DP(零钱兑换)
- ✅ 区间DP(最长回文子串)
- ✅ 状态压缩(位运算)
2. 贪心¶
- ✅ 区间调度(活动选择)
- ✅ 跳跃游戏
- ✅ 分配问题
3. 回溯¶
- ✅ 排列组合(全排列、组合)
- ✅ 搜索问题(N皇后、单词搜索)
4. 二分查找¶
- ✅ 标准二分(搜索插入位置)
- ✅ 二分答案(最小化最大值)
- ✅ 旋转数组(搜索旋转排序数组)
刷题优先级¶
必刷题(⭐⭐⭐⭐⭐)¶
- 两数之和
- 买卖股票的最佳时机
- 最大子数组和
- 三数之和
- 反转链表
- 合并两个有序链表
- 二叉树的最大深度
- 爬楼梯
- 打家劫舍
- 零钱兑换
- 最长递增子序列
- 课程表
- 括号生成
- 全排列
- 子集
- 单词搜索
- 岛屿数量
- LRU缓存
- 接雨水
- 滑动窗口最大值
高频题(⭐⭐⭐⭐)¶
- 最长公共前缀
- 验证二叉搜索树
- 层序遍历
- Pow(x, n)
- 反转链表II
- 四数之和
- 最长有效括号
- 组合
- 二叉树展开为链表
- 被围绕的区域
📝 总结¶
三大厂特点对比¶
| 公司 | 难度 | 重点 | 语言 | 风格 |
|---|---|---|---|---|
| 字节跳动 | ⭐⭐⭐⭐⭐ | DP、图、系统设计 | Python/Go | 偏难、创新 |
| 阿里巴巴 | ⭐⭐⭐⭐ | 数据库、分布式 | Java | 稳重、深入 |
| 腾讯 | ⭐⭐⭐⭐ | 网络、OS、数据结构 | C++/Java | 实用、基础 |
备考建议¶
1. 按公司准备¶
- 字节:重点刷DP和图算法
- 阿里:重点刷基础数据结构
- 腾讯:重点刷网络和OS相关
2. 时间分配¶
- 基础(1个月):数据结构 + 基础算法
- 进阶(1个月):DP + 图算法 + 回溯
- 冲刺(2周):公司真题 + 模拟面试
3. 模拟练习¶
- 每天限时做2-3题
- 模拟面试环境
- 记录错题和思路
祝你面试顺利,拿到心仪的Offer!💪🎉