1. 项目概述
今天要分享的是我在刷代码随想录Day03时遇到的四道经典算法题:283.移动零、844.比较含退格的字符串、977.有序数组的平方和209.长度最小的子数组。这几道题覆盖了数组操作、字符串处理、双指针技巧和滑动窗口等核心算法思想,非常适合用来巩固基础算法能力。
作为过来人,我深知算法学习过程中最痛苦的就是看着别人的解法"一看就会",自己动手却"一写就废"。这次我会用最直白的语言,结合自己踩过的坑,详细拆解每道题的解题思路和实现细节。无论你是刚开始刷题的新手,还是想复习基础的老手,相信都能从中获得启发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心题目解析
2.1 283.移动零
这道题要求我们将数组中的所有0移动到末尾,同时保持非零元素的相对顺序。看似简单,但要做到最优解需要一些技巧。
暴力解法思路:
最容易想到的是创建一个新数组,先放非零元素,再补零。但这样空间复杂度是O(n),不符合题目要求的原地操作。
双指针优化:
更优的解法是使用快慢指针:
- 慢指针指向当前需要放置非零元素的位置
- 快指针遍历数组,遇到非零元素就放到慢指针位置
- 最后将剩余位置补零
python复制def moveZeroes(nums):
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
for i in range(slow, len(nums)):
nums[i] = 0
注意:这里第二个循环必须从slow开始,而不是从0开始补零,否则会覆盖已放置的非零元素。
2.2 844.比较含退格的字符串
这道题要求我们比较两个包含退格符'#'的字符串是否相等,退格表示删除前一个字符。
栈解法:
最直观的思路是使用栈:
- 遇到普通字符入栈
- 遇到'#'就出栈
- 最后比较两个栈是否相等
python复制def backspaceCompare(s, t):
def build(s):
stack = []
for c in s:
if c != '#':
stack.append(c)
elif stack:
stack.pop()
return stack
return build(s) == build(t)
双指针优化:
为了达到O(1)空间复杂度,可以从后往前遍历:
- 维护两个指针和需要跳过的字符数
- 遇到'#'时skip增加
- 遇到普通字符时,如果有skip就跳过,否则比较字符
python复制def backspaceCompare(s, t):
i, j = len(s)-1, len(t)-1
skip_s = skip_t = 0
while i >= 0 or j >= 0:
# 处理s的退格
while i >= 0:
if s[i] == '#':
skip_s += 1
i -= 1
elif skip_s > 0:
skip_s -= 1
i -= 1
else:
break
# 处理t的退格
while j >= 0:
if t[j] == '#':
skip_t += 1
j -= 1
elif skip_t > 0:
skip_t -= 1
j -= 1
else:
break
# 比较字符
if i >= 0 and j >= 0:
if s[i] != t[j]:
return False
elif i >= 0 or j >= 0:
return False
i -= 1
j -= 1
return True
踩坑记录:双指针解法边界条件特别多,很容易漏掉i或j已经越界但另一个还没处理完的情况。
2.3 977.有序数组的平方
给定一个非递减顺序排序的整数数组,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
暴力解法:
先平方再排序,时间复杂度O(nlogn)。
双指针优化:
利用原数组有序的特性,最大值只可能出现在两端:
- 使用左右指针分别指向数组首尾
- 比较绝对值大小,将较大的平方放入结果数组末尾
- 移动相应指针
python复制def sortedSquares(nums):
n = len(nums)
result = [0] * n
left, right = 0, n - 1
pos = n - 1
while left <= right:
if abs(nums[left]) > abs(nums[right]):
result[pos] = nums[left] ** 2
left += 1
else:
result[pos] = nums[right] ** 2
right -= 1
pos -= 1
return result
经验分享:处理有序数组问题时,双指针往往能提供线性时间复杂度的解法。
2.4 209.长度最小的子数组
这道题要求找到满足其和≥target的长度最小的连续子数组。
暴力解法:
双重循环枚举所有子数组,时间复杂度O(n²)。
滑动窗口优化:
维护一个窗口,通过调整左右边界来寻找最优解:
- 右指针扩展窗口,直到sum≥target
- 记录当前窗口长度
- 左指针收缩窗口,尝试找到更小的窗口
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
常见错误:忘记处理没有满足条件的子数组时返回0的情况。
3. 解题技巧总结
3.1 双指针使用场景
- 对撞指针:用于有序数组的两端向中间遍历(如977题)
- 快慢指针:用于原地修改数组(如283题)
- 滑动窗口:用于寻找满足条件的子数组/子串(如209题)
3.2 边界条件处理
- 指针移动时注意数组越界
- 循环条件要仔细考虑"="的情况
- 特殊输入处理(如空数组、全零数组等)
3.3 调试技巧
- 打印中间变量值(如指针位置、当前和等)
- 使用小规模测试用例手动模拟
- 画图辅助理解指针移动过程
4. 刷题建议
- 每道题先自己思考暴力解法,再尝试优化
- 写代码前先用测试用例手动模拟算法流程
- 做完后对比不同解法的时空复杂度
- 定期复习做过的题目,避免遗忘
这四道题虽然不算很难,但涵盖了数组和字符串处理的多个重要技巧。我在最初刷题时,经常因为边界条件没考虑周全而卡住,后来发现画图+小规模测试是最有效的调试方法。现在回头看这些题,感觉思路清晰多了,希望我的经验能帮你少走些弯路。
