1. 算法训练营开营:二分查找与数组操作实战
作为一名经历过多次算法面试的老兵,我深知二分查找和数组操作是每个程序员必须跨过的第一道坎。今天我们就从代码随想录算法训练营的第一天内容开始,手把手带你掌握704.二分查找、27.移除元素和977.有序数组的平方这三道经典题目。
这三道题看似简单,却涵盖了算法学习中最关键的几个思维模式:边界条件处理、双指针技巧和原地操作思想。我在大厂面试中不止一次看到它们的变种,就连ACM金牌选手也建议从这些基础题开始打磨编码手感。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 704.二分查找深度剖析
2.1 算法原理与边界陷阱
二分查找的核心思想是"减而治之",每次将搜索范围缩小一半。但就是这个看似简单的算法,在真实面试中的正确率不足30%。最常见的陷阱出现在边界条件处理上:
python复制def search(nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 关键点1:右边界初始值
while left <= right: # 关键点2:循环条件
mid = left + (right - left) // 2 # 关键点3:防溢出写法
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # 关键点4:左边界更新
else:
right = mid - 1 # 关键点5:右边界更新
return -1
实战经验:在ACM比赛中,我们通常会准备三种二分模板:精确查找、左边界查找和右边界查找。初学者建议先用纸笔模拟运行过程,特别注意当数组长度为偶数时mid的取值偏向。
2.2 复杂度分析与变种题型
时间复杂度O(log n)的背后是每次迭代将问题规模减半的威力。实际工程中,二分查找的应用远不止于有序数组:
- 在MySQL的B+树索引中查找记录
- 分布式系统中的一致性哈希路由
- 机器学习模型超参数搜索
最近流行的"二分查找pta函数"考察的就是对STL中lower_bound和upper_bound的实现理解。建议在掌握基础版本后,尝试实现这两个变种:
python复制# 寻找第一个不小于target的元素位置
def lower_bound(nums, target):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
3. 27.移除元素的双指针艺术
3.1 暴力解法与优化思路
这道题要求原地移除指定值的元素,返回新长度。新手最容易想到的是O(n²)的暴力解法:
python复制def removeElement(nums: List[int], val: int) -> int:
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)
但面试官期待的往往是O(n)时间复杂度的双指针解法。这与jQuery数组移除某个元素的思路异曲同工:
3.2 快慢指针的经典实现
python复制def removeElement(nums: List[int], val: int) -> int:
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
调试技巧:在IDE中单步执行时,可以打印出每次循环后的数组状态。特别注意当连续出现多个val值时指针的移动情况。
这种快慢指针模式在解决数组去重、移动零等问题时同样适用。我在LeetCode周赛中就曾用类似思路解决了数组重排问题。
4. 977.有序数组的平方的三种解法
4.1 暴力排序法及其局限
最直观的思路是先平方后排序:
python复制def sortedSquares(nums: List[int]) -> List[int]:
return sorted(x*x for x in nums)
虽然简洁,但O(nlogn)时间复杂度在面试中难以过关。这引出了双指针解法的必要性。
4.2 双向指针的优雅解法
利用原数组已排序的特性,我们可以从两端向中间遍历:
python复制def sortedSquares(nums: List[int]) -> List[int]:
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 工程实践中的优化
实际项目中,当数组较大时可以考虑以下优化:
- 使用numpy的向量化运算加速平方计算
- 对于绝对值相同的元素批量处理
- 提前终止:当左指针超过右指针时直接填充剩余元素
5. 常见错误与调试技巧
5.1 二分查找的坑点记录
根据代码随想录学员的提交统计,最常见的错误包括:
- 循环条件写成
while left < right导致漏判边界 - 更新边界时直接
right = mid造成死循环 - 未处理空数组输入导致索引越界
建议在代码开头添加防御性检查:
python复制if not nums:
return -1
5.2 双指针问题的调试方法
当双指针逻辑出现问题时,可以:
- 在循环内打印指针位置和数组状态
- 使用小规模测试用例(如[1,2,3])逐步验证
- 特别注意指针移动的条件和步长
我在教学过程中发现,用不同颜色标记快慢指针的移动路径能显著提升理解效率。
6. 扩展训练与资源推荐
为了巩固这些基础知识,建议尝试以下变种题目:
- 34.在排序数组中查找元素的第一个和最后一个位置
- 26.删除有序数组中的重复项
- 283.移动零
代码随想录网站提供了这些题目的动画讲解,比纯文字更直观。对于想系统提升的同学,可以按照以下路线图学习:
- 先掌握数组和链表的基本操作
- 然后攻克双指针和滑动窗口
- 最后挑战更复杂的动态规划
我个人的刷题经验是:每个经典题型至少手写3遍,第一遍看答案,第二遍独立实现,第三遍尝试优化。坚持30天后,你会明显感觉编码思维变得清晰。
