🔢 数组基础 — 双面夹击与最大子数组

数组是算法的起点,掌握数组操作是通关面试的第一步

📖 数组的核心概念

数组(Array)是最基础的数据结构,在内存中连续存储相同类型的元素。数组的随机访问时间复杂度为 O(1),插入/删除需要移动元素,时间复杂度为 O(n)。面试中,数组问题常常结合双指针、哈希表、前缀和等技巧来优化。

数组的内存模型: 地址: 0x100 0x104 0x108 0x10C 0x110 索引: 0 1 2 3 4 数据: [10] [20] [30] [40] [50] ↑ ↑ 首地址 末地址 特点: 连续内存 → 随机访问 O(1) 插入/删除 → 需要移动元素 O(n)

1. 数组的常见操作模式

遍历模式:线性扫描是数组最基本的操作。正向遍历、反向遍历、双端遍历各有用途。
正向:从左到右,适合前缀计算
反向:从右到左,适合后缀计算
双端:两端向中间,适合对称操作
原地修改:面试高频考点!在原数组上直接修改,不使用额外空间。
关键:用读写指针分离,读指针在前探路,写指针记录有效位置
典型:删除元素、去重、移动零

2. Python 数组操作速查

# 初始化
arr = [1, 2, 3, 4, 5]
arr = [0] * 10           # 固定大小

# 访问与修改
x = arr[0]               # O(1) 访问
arr[0] = 10              # O(1) 修改

# 添加与删除
arr.append(6)            # O(1) 均摊
arr.pop()                # O(1) 删除末尾
arr.insert(0, 0)         # O(n) 头部插入
arr.pop(0)               # O(n) 头部删除

# 切片
sub = arr[1:4]           # [arr[1], arr[2], arr[3]]
rev = arr[::-1]          # 反转

# 排序与查找
arr.sort()               # 原地排序 O(n log n)
idx = arr.index(3)       # 查找 O(n)
Python 的 list 底层是动态数组,自动扩容。平均 append 是 O(1),但扩容时单次 O(n)。面试中如果需要严格 O(1) 的头部操作,考虑 collections.deque

🎯 题目一:两数之和 (LC 1)

题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值的两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,且同一个元素不能使用两遍。

示例: nums = [2, 7, 11, 15], target = 9 索引: 0 1 2 3 数值: [2] [7] [11] [15] ↑ ↑ 2 + 7 = 9 ✓ → 返回 [0, 1]

思路分析

暴力法(不推荐):两层循环枚举所有数对,检查和是否等于 target。
时间复杂度:O(n²),空间复杂度:O(1)
面试官会要求优化!
哈希表法(最优):遍历数组,对于每个元素 num,检查 target - num 是否已经在哈希表中。
如果存在,直接返回两个下标;如果不存在,将当前元素及其下标存入哈希表。
时间复杂度:O(n),空间复杂度:O(n)

代码实现

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        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 []

复杂度分析

时间复杂度:O(n) — 只需一次遍历,哈希表查找 O(1)

空间复杂度:O(n) — 最坏情况存入 n-1 个元素

变体练习

🎯 题目二:存在重复元素 (LC 217)

题目描述:给你一个整数数组 nums。如果任一值在数组中出现至少两次,返回 true;如果数组中每个元素互不相同,返回 false

示例: nums = [1, 2, 3, 1] 1 → 出现2次 → 返回 True 示例: nums = [1, 2, 3, 4] 所有元素唯一 → 返回 False

思路分析

方法一:哈希集合:遍历数组,用集合记录已见元素。如果当前元素已在集合中,返回 True。
时间复杂度:O(n),空间复杂度:O(n)
方法二:排序后比较相邻:先排序,然后检查相邻元素是否相同。
时间复杂度:O(n log n),空间复杂度:O(1)(原地排序时)
方法三:利用集合长度:比较集合大小和数组长度。
一行代码:return len(set(nums)) != len(nums)
时间复杂度:O(n),空间复杂度:O(n)

代码实现

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        seen = set()
        for num in nums:
            if num in seen:
                return True
            seen.add(num)
        return False

复杂度分析

时间复杂度:O(n) — 单次遍历 + O(1) 集合操作

空间复杂度:O(n) — 最坏存入 n 个元素

变体练习

🎯 题目三:最大子数组和 (LC 53)

题目描述:给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] 子数组 [4, -1, 2, 1] 和为 6 → 最大 Kadane算法过程: i=0: cur=-2, best=-2 i=1: cur=max(1, -2+1)=1, best=1 i=2: cur=max(-3, 1-3)=-2, best=1 i=3: cur=max(4, -2+4)=4, best=4 i=4: cur=max(-1, 4-1)=3, best=4 i=5: cur=max(2, 3+2)=5, best=5 i=6: cur=max(1, 5+1)=6, best=6 ← 最优解 i=7: cur=max(-5, 6-5)=1, best=6 i=8: cur=max(4, 1+4)=5, best=6

思路分析

暴力法:枚举所有子数组,计算每个子数组的和。
时间复杂度:O(n²)O(n³),面试必被要求优化。
Kadane算法(动态规划思想):
核心思想:以 i 结尾的最大子数组和,要么是 nums[i] 本身,要么是 dp[i-1] + nums[i]
状态转移:dp[i] = max(nums[i], dp[i-1] + nums[i])
空间优化:只需一个变量记录当前子数组和,另一个记录全局最大值。
时间复杂度:O(n),空间复杂度:O(1)

代码实现

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        cur_sum = best_sum = nums[0]
        for num in nums[1:]:
            cur_sum = max(num, cur_sum + num)
            best_sum = max(best_sum, cur_sum)
        return best_sum

复杂度分析

时间复杂度:O(n) — 一次遍历

空间复杂度:O(1) — 只用两个变量

Kadane 算法的关键:当 cur_sum 变为负数时,它不可能对后续子数组有正贡献,所以直接丢弃,从当前元素重新开始。这等价于动态规划中 dp[i] = max(nums[i], dp[i-1] + nums[i])。

变体练习

成就解锁:数组先锋 — 掌握哈希查找、去重检测、Kadane最大子数组三大核心技巧
LeetCode AC验证:LC 1 Two Sum ✅ | LC 217 Contains Duplicate ✅ | LC 53 Maximum Subarray ✅

📝 课后练习

  1. LC 26 删除有序数组中的重复项(原地双指针)
  2. LC 27 移除元素(读写指针)
  3. LC 66 加一(数组模拟加法)
  4. LC 283 移动零(快慢指针变种)
  5. LC 485 最大连续1的个数(简单计数)

🔑 本课要点回顾

📚 扩展阅读

思考题:如果要求最大子数组的起止位置,Kadane算法该如何修改?提示:在 cur_sum 被重置时记录新起点,在 best_sum 被更新时记录最优区间。

🔬 数组的高级技巧

数组看似简单,但面试中的数组题往往暗藏玄机。这里补充几个重要的数组高级技巧,为后续课程做铺垫。

1. 原地哈希

当数组元素范围在 [1, n] 时,可以把数组本身当作哈希表使用。通过将元素值映射到索引(nums[i] → 索引 nums[i]-1),实现 O(1) 空间的检测。

# 原地哈希: 找到所有出现两次的数
# nums[i] 应该在 nums[nums[i]-1] 的位置
def findDuplicates(nums):
    result = []
    for num in nums:
        idx = abs(num) - 1
        if nums[idx] < 0:
            result.append(abs(num))
        else:
            nums[idx] = -nums[idx]
    return result

2. 摩尔投票法

在 O(1) 空间内找多数元素(出现次数 > n/2 的元素)。核心思想:不同元素两两抵消,最后剩下的就是多数元素。

def majorityElement(nums):
    count = 0
    candidate = None
    for num in nums:
        if count == 0:
            candidate = num
        count += 1 if num == candidate else -1
    return candidate
摩尔投票法可以推广到找出现次数 > n/k 的元素,只需维护 k-1 个候选者。这是 LC 229 求众数 II 的解法。