1. 算法刷题实战:代码随想录Day03四题精解
今天要拆解的是代码随想录Day03的四道经典题目:283.移动零、844.比较含退格的字符串、977.有序数组的平方、209.长度最小的子数组。这组题目覆盖了双指针、字符串处理、滑动窗口等核心技巧,特别适合用来检验基础算法的掌握程度。我在刷题过程中发现,这几道题看似简单,但要做到最优解且bug-free并不容易。下面我会结合自己的踩坑经验,详细分析每道题的解题思路和实现细节。
1.1 题目概览与核心技巧
这四道题目看似独立,实则存在内在联系:
- 283和977都使用了双指针技巧,但应用场景完全不同
- 844考察字符串处理的边界条件把控能力
- 209则是滑动窗口的典型应用场景
- 所有题目都要求O(n)时间复杂度,需要避免暴力解法
我在第一次刷这组题时,平均每道题都提交了3次才通过所有测试用例。主要问题集中在:指针移动条件判断不严谨、窗口边界更新不及时、退格字符处理逻辑存在漏洞等。接下来我们逐题分析最优解法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 283.移动零 - 双指针的经典应用
2.1 问题重述
给定一个数组nums,编写函数将所有0移动到数组末尾,同时保持非零元素的相对顺序。要求:
- 必须在原数组上操作
- 尽量减少操作次数
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
2.2 解法分析
初始思路误区
我最初尝试用类似冒泡排序的方法,遇到0就与后一个元素交换。这种方法虽然可行,但时间复杂度达到O(n²),明显不是最优解。
最优解法:双指针
维护两个指针:
- 慢指针slow:指向下一个非零元素应该存放的位置
- 快指针fast:遍历数组寻找非零元素
python复制def moveZeroes(nums):
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
关键点说明
- 当fast遇到非零元素时,与slow位置交换
- 无论是否交换,fast每次都前进
- 只有发生交换时slow才前进
注意:这里交换操作可以优化为直接赋值,因为fast后面的元素最终都会被覆盖或置零。但实际测试发现,Python中交换操作的性能差异不大。
2.3 复杂度分析
- 时间复杂度:O(n),只需遍历一次数组
- 空间复杂度:O(1),原地操作
2.4 常见错误
- 忘记移动slow指针导致覆盖错误
- 交换条件判断错误(如写成nums[slow] != 0)
- 最后没有将剩余位置补零(当不需要保持原非零元素时)
3. 844.比较含退格的字符串 - 字符串处理的艺术
3.1 问题描述
给定S和T两个字符串,其中'#'表示退格字符。判断两者是否相等。
示例:
输入:S = "ab#c", T = "ad#c"
输出:true
解释:两者都变成"ac"
3.2 解法对比
方法一:栈处理(直观解法)
python复制def backspaceCompare(S, T):
def build(s):
stack = []
for c in s:
if c != '#':
stack.append(c)
elif stack:
stack.pop()
return ''.join(stack)
return build(S) == build(T)
方法二:双指针(优化空间)
python复制def backspaceCompare(S, T):
i, j = len(S)-1, len(T)-1
skipS = skipT = 0
while i >= 0 or j >= 0:
# 处理S的退格
while i >= 0:
if S[i] == '#':
skipS += 1
i -= 1
elif skipS > 0:
skipS -= 1
i -= 1
else:
break
# 处理T的退格
while j >= 0:
if T[j] == '#':
skipT += 1
j -= 1
elif skipT > 0:
skipT -= 1
j -= 1
else:
break
# 比较字符
if i >= 0 and j >= 0 and S[i] != T[j]:
return False
# 一个字符串已遍历完
if (i >= 0) != (j >= 0):
return False
i -= 1
j -= 1
return True
方法对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 栈 | O(m+n) | O(m+n) | 代码简洁 |
| 双指针 | O(m+n) | O(1) | 内存敏感 |
实际面试中,建议先实现栈方法,再优化为双指针。我在第一次实现时忽略了连续多个'#'的情况,导致结果错误。
3.3 边界条件
- 开头就是'#'(如"#a#b")
- 连续多个'#'(如"a###b")
- 空字符串比较(如""和"#")
4. 977.有序数组的平方 - 双指针的巧妙运用
4.1 问题重述
给定按非递减顺序排序的整数数组nums,返回每个数字平方后组成的新数组,要求也按非递减顺序排序。
示例:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
4.2 解法演进
暴力解法的问题
直接平方后排序:
python复制def sortedSquares(nums):
return sorted(x*x for x in nums)
时间复杂度O(nlogn),没有利用输入数组已排序的特性。
最优解:双指针
利用原数组有序的特性,最大值只可能出现在两端:
python复制def sortedSquares(nums):
n = len(nums)
result = [0] * n
left, right = 0, n-1
for i in range(n-1, -1, -1):
if abs(nums[left]) > abs(nums[right]):
result[i] = nums[left] ** 2
left += 1
else:
result[i] = nums[right] ** 2
right -= 1
return result
关键点
- 比较绝对值而非原始值(负数可能更大)
- 从后向前填充结果数组
- 指针移动条件判断
4.3 复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)(输出数组不计入则为O(1))
4.4 易错点
- 忘记处理负数情况,直接比较原始值
- 结果数组填充方向错误(从前向后会导致需要额外排序)
- 指针移动条件写反(导致结果逆序)
5. 209.长度最小的子数组 - 滑动窗口精讲
5.1 问题描述
给定一个含有n个正整数的数组和一个正整数target,找出数组中满足其和≥target的长度最小的连续子数组,返回其长度。
示例:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组[4,3]满足条件
5.2 滑动窗口实现
基础实现
python复制def minSubArrayLen(target, nums):
n = len(nums)
min_len = float('inf')
left = 0
current_sum = 0
for right in range(n):
current_sum += nums[right]
while current_sum >= target:
min_len = min(min_len, right - left + 1)
current_sum -= nums[left]
left += 1
return min_len if min_len != float('inf') else 0
优化点
- 提前终止:当min_len=1时可直接返回
- 前缀和+二分查找:适用于数组元素较大的情况
5.3 复杂度分析
- 时间复杂度:O(n),每个元素最多被访问两次
- 空间复杂度:O(1)
5.4 常见问题
- 窗口收缩条件错误(使用if而非while)
- 忘记处理无解情况(应返回0)
- 最小长度初始化值设置不当
6. 刷题经验总结
经过这四道题的实战,我总结了以下几点经验:
-
双指针的三种常见模式:
- 同向移动(快慢指针)
- 相向移动(左右指针)
- 滑动窗口(动态调整区间)
-
边界条件检查清单:
- 空输入处理
- 单元素情况
- 全相同元素
- 极值测试(大数/小数)
-
调试技巧:
- 打印指针位置和关键变量
- 使用小测试用例手动模拟
- 检查循环不变式
-
优化思路:
- 先写出正确解再优化
- 分析重复计算部分
- 考虑数据特性(如已排序)
在实际面试中,建议按照以下步骤解题:
- 明确问题要求(包括时间/空间复杂度)
- 举例说明理解是否正确
- 提出暴力解法并分析不足
- 逐步优化解法
- 编写代码并测试边界条件
这组题目虽然属于easy/medium难度,但涵盖了面试中最常考察的基础算法思想。建议每个题目至少手写3遍,直到能够10分钟内bug-free实现。对于滑动窗口这类技巧,可以尝试解决更多变种问题如:
- 904.水果成篮
- 76.最小覆盖子串
- 3.无重复字符的最长子串
最后分享一个实用技巧:在IDE中设置常用测试用例模板,可以节省调试时间。例如对于滑动窗口问题,我会准备以下测试用例:
- 常规情况
- 目标值特别大/小
- 数组包含重复元素
- 空数组情况
- 解在开头/结尾的情况
