1. 算法训练营开营:经典题目实战解析
作为程序员,算法能力就像厨师的刀工——看似基础却决定职业上限。今天带大家拆解代码随想录训练营首日的三道经典题目:704二分查找、27移除元素、977有序数组的平方。这三道题覆盖了数组操作的三大核心场景:查找、删除和转换,是每个算法新手必须攻克的"铁人三项"。
我在大厂面试中常拿这三题作为初筛标准,因为它们能快速暴露候选人的基础缺陷。比如二分查找写不出O(logn)解法、移除元素时空间复杂度失控、数组平方后不会利用有序性优化...这些坑我当年都踩过。下面分享经过实战检验的解题套路,附带调试时发现的典型错误案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 704. 二分查找:细节决定成败
2.1 算法原理与边界陷阱
二分查找看似简单,但90%的初学者会栽在边界条件上。核心思想是通过不断缩小搜索范围来定位目标值,就像查字典时不会从第一页逐个翻找。关键变量:
- left/right:当前搜索范围的左右边界
- mid:中间位置,计算方式决定算法生死
致命细节:mid = (left + right) // 2 在极端情况下会导致整数溢出。更安全的写法是 mid = left + (right - left) // 2
2.2 两种常见写法对比
写法一(左闭右闭区间):
python复制def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right: # 注意等号
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # 明确排除mid
else:
right = mid - 1
return -1
写法二(左闭右开区间):
python复制def search(nums, target):
left, right = 0, len(nums)
while left < right: # 不等号
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid # 不排除mid
return -1
两种写法的差异就像用尺子测量时的包含/不包含端点,必须保持逻辑自洽。我建议新手先用第一种,更符合直觉。
2.3 调试记录:那些年我犯过的错
- 死循环案例:忘记更新left/right导致区间不收缩
- 漏判案例:while条件写成 < 但区间是闭区间
- 偏移错误:mid计算使用 >>1 却忘记括号优先级
3. 27. 移除元素:双指针的启蒙课
3.1 暴力解法与优化空间
最直接的思路是发现目标值后,将后续元素全部前移。这种O(n^2)解法在面试中只能算及格分:
python复制def removeElement(nums, val):
i = 0
while i < len(nums):
if nums[i] == val:
for j in range(i+1, len(nums)):
nums[j-1] = nums[j]
nums.pop()
else:
i += 1
return len(nums)
3.2 快慢指针的精妙之处
通过维护慢指针(写位置)和快指针(读位置),可以降到O(n)时间复杂度:
python复制def removeElement(nums, val):
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
这个模式就像工厂流水线的质检员(slow)和传送带(fast),不合格产品直接跳过不装箱。我在首次实现时犯过这些错:
- 忘记移动slow指针导致覆盖写入
- 返回slow+1造成边界溢出
- 修改原数组但没处理剩余元素
4. 977. 有序数组的平方:指针碰撞的艺术
4.1 暴力法的局限性
直接平方后排序的解法时间复杂度O(nlogn),没有利用输入数组已排序的特性:
python复制def sortedSquares(nums):
return sorted(x*x for x in nums)
4.2 双指针逆向填充法
利用最大值只可能出现在数组两端的特性,实现O(n)解法:
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
这个解法就像两个人从隧道两端向中间走,每次选择绝对值较大的数平方后放入结果数组末尾。调试时需要注意:
- 比较绝对值而非原始值
- 结果数组要从后往前填充
- 循环终止条件包含等号
5. 综合训练:三题的内在联系
这三道题实际上是递进关系:二分查找训练边界控制能力→移除元素引入双指针思想→有序数组平方结合了前两者的技巧。建议按这个顺序反复练习,直到能闭眼写出无bug代码。
我的训练心得:
- 先用纸笔模拟运行过程,画出指针变化
- 故意制造错误案例验证边界条件
- 记录每种写法的循环不变量(Loop Invariant)
- 对比不同语言的实现差异(如C++的迭代器与Python索引)
最后分享一个调试技巧:在循环内打印指针位置和关键变量,像这样:
python复制print(f"left={left}, right={right}, mid={mid}, nums[mid]={nums[mid]}")
