1. 算法训练营第二天的核心挑战
作为一名参加过多次算法训练营的老学员,我清楚地记得第二天往往是第一个分水岭。当天的四道题目看似风格迥异,实则暗藏玄机——它们分别考察了滑动窗口、矩阵模拟、前缀和以及贪心算法这四大经典解题范式。
209.长度最小的子数组是滑动窗口的经典应用题,要求我们在O(n)时间复杂度内找到满足条件的最小连续子数组。59.螺旋矩阵II则考验对二维矩阵的精确控制能力,需要模拟填充过程。区间和问题通常需要借助前缀和技巧来优化查询效率。而开发商购买土地这道商业背景的题目,实际上考察的是区间合并与贪心选择的结合运用。
提示:建议按209→59→区间和→土地购买的顺序解题,这个排列由易到难,符合认知曲线。
2. 209.长度最小的子数组的滑动窗口精解
2.1 问题重述与暴力解法分析
给定一个含n个正整数的数组和正整数target,找出数组中满足其和≥target的长度最小的连续子数组。如不存在符合条件的子数组则返回0。
最直观的暴力解法是双重循环枚举所有子数组:
python复制def minSubArrayLen(target, nums):
min_len = float('inf')
n = len(nums)
for i in range(n):
current_sum = 0
for j in range(i, n):
current_sum += nums[j]
if current_sum >= target:
min_len = min(min_len, j-i+1)
break
return min_len if min_len != float('inf') else 0
这种解法时间复杂度O(n²),在LeetCode上会超时。我在首次尝试时就被这个陷阱卡住,直到发现滑动窗口这个优化方向。
2.2 滑动窗口的优化实现
滑动窗口通过维护动态变化的窗口来避免重复计算。具体实现时要注意:
- 窗口左右边界初始化为0
- 右边界逐步扩展并累加和
- 当和≥target时,尝试左边界收缩
优化后的代码:
python复制def minSubArrayLen(target, nums):
left = total = 0
min_len = float('inf')
for right in range(len(nums)):
total += nums[right]
while total >= target:
min_len = min(min_len, right-left+1)
total -= nums[left]
left += 1
return min_len if min_len != float('inf') else 0
这个版本时间复杂度降为O(n),空间复杂度O(1)。实测在LeetCode上运行时间从暴力解的4000ms+降到40ms左右。
2.3 边界条件与调试技巧
有几个易错点需要特别注意:
- 空数组输入情况
- 所有元素和仍小于target的情况
- 单个元素就满足条件的情况
调试时可以打印窗口变化过程:
python复制[2,3,1,2,4,3], target=7
窗口[2] sum=2
窗口[2,3] sum=5
窗口[2,3,1] sum=6
窗口[2,3,1,2] sum=8 → 记录长度4
窗口[3,1,2] sum=6
窗口[3,1,2,4] sum=10 → 更新长度4→3
窗口[1,2,4] sum=7 → 更新长度3→3
...
3. 59.螺旋矩阵II的模拟艺术
3.1 问题理解与解题思路
给定正整数n,生成一个包含1到n²所有元素的n×n螺旋矩阵。例如n=3时:
code复制[
[1,2,3],
[8,9,4],
[7,6,5]
]
这类矩阵模拟题的关键在于:
- 确定填充方向顺序(右→下→左→上循环)
- 设置边界变量控制填充范围
- 处理奇数n时的中心点特殊情况
3.2 分层填充法的实现细节
我采用的"洋葱剥皮法"逐层填充:
python复制def generateMatrix(n):
matrix = [[0]*n for _ in range(n)]
left, right, top, bottom = 0, n-1, 0, n-1
num = 1
while left <= right and top <= bottom:
# 从左到右填充上层
for i in range(left, right+1):
matrix[top][i] = num
num += 1
top += 1
# 从上到下填充右层
for i in range(top, bottom+1):
matrix[i][right] = num
num += 1
right -= 1
# 从右到左填充下层
for i in range(right, left-1, -1):
matrix[bottom][i] = num
num += 1
bottom -= 1
# 从下到上填充左层
for i in range(bottom, top-1, -1):
matrix[i][left] = num
num += 1
left += 1
return matrix
3.3 调试时的可视化技巧
对于n=4的情况,可以打印填充过程:
code复制Step1:填充第1层外圈
1 2 3 4
12 5
11 6
10 9 8 7
Step2:填充第2层内圈
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
4. 区间和问题的前缀和妙用
4.1 问题场景分析
区间和问题通常描述为:给定数组,需要频繁查询某个区间[i,j]的元素和。直接遍历计算的O(n)查询效率在多次查询时性能低下。
4.2 前缀和数组的构建
预处理阶段构建前缀和数组:
python复制def prefixSum(nums):
prefix = [0]*(len(nums)+1)
for i in range(len(nums)):
prefix[i+1] = prefix[i] + nums[i]
return prefix
这样区间和query(i,j) = prefix[j+1] - prefix[i]
4.3 实际应用中的优化
当数组可能动态变化时,可以考虑更高级的数据结构:
- 树状数组(Binary Indexed Tree)
- 线段树(Segment Tree)
例如使用树状数组实现:
python复制class BIT:
def __init__(self, nums):
self.n = len(nums)
self.tree = [0]*(self.n+1)
for i in range(self.n):
self.update(i+1, nums[i])
def update(self, index, delta):
while index <= self.n:
self.tree[index] += delta
index += index & -index
def query(self, index):
res = 0
while index > 0:
res += self.tree[index]
index -= index & -index
return res
def rangeQuery(self, l, r):
return self.query(r) - self.query(l-1)
5. 开发商购买土地的贪心策略
5.1 问题抽象与建模
题目描述开发商有一笔预算,需要购买连续的土地块,要求总价格不超过预算且土地数量最多。这实际上可以转化为:
给定正数数组prices和预算money,找到最长的连续子数组使其元素和≤money。
5.2 滑动窗口的变形应用
这与209题异曲同工,但求的是最大窗口而非最小:
python复制def maxLand(prices, money):
left = max_len = current_sum = 0
for right in range(len(prices)):
current_sum += prices[right]
while current_sum > money:
current_sum -= prices[left]
left += 1
max_len = max(max_len, right-left+1)
return max_len
5.3 商业场景的扩展思考
实际工程中可能还需要考虑:
- 土地块的非连续选择
- 多目标优化(如同时考虑面积和位置)
- 动态价格变化的情况
这类问题在资源分配、投资组合优化等领域有广泛应用。
6. 训练营的进阶学习建议
完成这四道题目后,建议延伸学习:
- 滑动窗口的变种:
- 904.水果成篮
- 76.最小覆盖子串
- 矩阵模拟:
- 54.螺旋矩阵
- 48.旋转图像
- 前缀和应用:
- 560.和为K的子数组
- 304.二维区域和检索
我在刷题过程中总结的笔记方法是:对每道题记录三种解法思路(最优解、次优解、暴力解),并标注时间/空间复杂度。这样复习时可以快速回顾解题脉络。
