跳转至

441_数组与哈希表面试题


一句话说明

数组和哈希表是最基础的数据结构,哈希表用空间换时间,把 O(n) 查找变成 O(1),是优化暴力解法的第一反应。


核心知识点

白话理解

数组像一排座位,按位置找人 O(1) 但按名字找人 O(n)。哈希表像花名册,直接按名字查 O(1),代价是需要额外空间存花名册。面试里凡是"找重复""找配对""统计频率",第一反应就是哈希表。

常用操作时间复杂度

操作数组哈希表(平均)
访问O(1)O(1)
查找O(n)O(1)
插入末尾O(1)O(1)
插入中间O(n)O(1)
删除O(n)O(1)

经典题目与解法

题目1:两数之和(LeetCode 1,经典哈希)

题意:给定数组 nums 和目标值 target,找出两个数的下标使其和等于 target。

def two_sum(nums, target):
    """
    暴力法 O(n²),哈希法 O(n)
    思路:遍历时用哈希表记录"之前见过的数",
    对当前数 num,查哈希表里有没有 target-num
    """
    seen = {}                              # 哈希表:{数值: 下标}

    for i, num in enumerate(nums):
        complement = target - num          # 需要的另一个数

        if complement in seen:             # 在之前见过的数里找到了
            return [seen[complement], i]   # 返回两个下标

        seen[num] = i                      # 记录当前数和它的下标

    return []                              # 没有找到(题目保证有解)

# 测试
print(two_sum([2, 7, 11, 15], 9))   # [0, 1](2+7=9)
print(two_sum([3, 2, 4], 6))        # [1, 2](2+4=6)
print(two_sum([3, 3], 6))           # [0, 1](3+3=6)

时间复杂度:O(n) 一次遍历
空间复杂度:O(n) 哈希表最多存 n 个元素


题目2:字母异位词分组(LeetCode 49)

题意:给定字符串列表,把字母异位词(字母相同、顺序不同的词)分组。

from collections import defaultdict

def group_anagrams(strs):
    """
    思路:字母异位词排序后一定相同('eat' 和 'tea' 排序后都是 'aet')
    用排序结果作哈希表的键,相同键的词分到一组
    """
    groups = defaultdict(list)             # 默认值是空列表

    for word in strs:
        key = ''.join(sorted(word))        # 排序作为键:'eat'→'aet'
        groups[key].append(word)           # 加入对应分组

    return list(groups.values())           # 返回所有分组

# 测试
words = ["eat", "tea", "tan", "ate", "nat", "bat"]
result = group_anagrams(words)
print(result)
# [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

时间复杂度:O(n × k log k),n 是词数,k 是最长词长度(排序)
空间复杂度:O(n × k)

进阶优化:用字母计数替代排序,复杂度降到 O(n × k)

def group_anagrams_v2(strs):
    groups = defaultdict(list)
    for word in strs:
        count = [0] * 26                   # 26个字母的计数
        for c in word:
            count[ord(c) - ord('a')] += 1  # 统计每个字母出现次数
        key = tuple(count)                 # 用计数元组作键(可哈希)
        groups[key].append(word)
    return list(groups.values())


题目3:最长连续序列(LeetCode 128,哈希集合)

题意:给定未排序的整数数组,找出最长的连续数字序列的长度,要求 O(n)。

def longest_consecutive(nums):
    """
    思路:用集合存所有数字,对每个序列的起点(num-1不在集合中)
    开始向后数,统计序列长度
    """
    num_set = set(nums)                    # 转成集合,O(1)查找
    max_len = 0

    for num in num_set:
        if num - 1 not in num_set:         # 只从序列起点开始数
            current = num
            length = 1

            while current + 1 in num_set:  # 向后延伸
                current += 1
                length += 1

            max_len = max(max_len, length)

    return max_len

# 测试
print(longest_consecutive([100, 4, 200, 1, 3, 2]))  # 4(1,2,3,4)
print(longest_consecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1]))  # 9

时间复杂度:O(n) 每个数最多被访问两次(外循环 + while 循环)
空间复杂度:O(n) 集合存储


面试技巧

  1. 看到"找配对/补数"就想哈希表:complement = target - num 是万能套路
  2. 集合去重比排序快:去重用 set,查存在 O(1) 比排序 O(n log n) 快
  3. defaultdict 省去初始化判断defaultdict(list)if key not in d: d[key] = [] 优雅
  4. 说清楚时间/空间权衡:哈希表用空间换时间,面试官期待你主动提
  5. Counter 统计频率神器from collections import Counter; Counter(nums) 一行搞定

速查表

# 哈希表常用操作
d = {}
d[key] = value          # 插入/更新
value = d.get(key, 0)   # 安全取值,不存在返回0
key in d                # 判断存在,O(1)

# defaultdict 自动初始化
from collections import defaultdict
d = defaultdict(int)    # 默认值0
d = defaultdict(list)   # 默认值[]
d = defaultdict(set)    # 默认值set()

# Counter 计频
from collections import Counter
freq = Counter([1,1,2,3,3,3])  # {3:3, 1:2, 2:1}
freq.most_common(2)             # [(3,3), (1,2)] 最常见的2个

# 集合操作
s = set(nums)           # 去重
x in s                  # O(1)查找
s1 & s2                 # 交集
s1 | s2                 # 并集
s1 - s2                 # 差集
题型思路工具
两数之和哈希记录已见数dict
找重复哈希计数Counter
分组/归类哈希 key 分组defaultdict(list)
去重+快速查找集合set
前缀和查询哈希记前缀dict