1. 基础算法快速复习指南
作为程序员面试和日常开发中最常考察的基础算法知识,排序、二分、前缀和、差分、双指针这五大算法家族构成了算法能力的基石。我在准备技术面试和实际项目开发过程中,发现这些算法出现的频率高达80%以上。本文将用最精炼的方式带大家快速复习这些核心算法,并附上典型例题帮助理解。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 排序算法精要
2.1 常见排序算法对比
排序算法是数据结构中最基础的部分,下表列出了面试中最常考察的几种排序算法及其特性:
| 算法名称 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(1) | 稳定 | 教学示例 |
| 选择排序 | O(n²) | O(1) | 不稳定 | 简单实现 |
| 插入排序 | O(n²) | O(1) | 稳定 | 小规模数据 |
| 快速排序 | O(nlogn) | O(logn) | 不稳定 | 通用排序 |
| 归并排序 | O(nlogn) | O(n) | 稳定 | 链表排序 |
| 堆排序 | O(nlogn) | O(1) | 不稳定 | 优先级队列 |
提示:在实际面试中,快速排序和归并排序的实现是必考内容,建议重点掌握。
2.2 快速排序实现要点
快速排序的核心思想是分治,以下是C++实现的关键代码:
cpp复制void quickSort(vector<int>& nums, int left, int right) {
if (left >= right) return;
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[right];
int i = left;
for (int j = left; j < right; ++j) {
if (nums[j] < pivot) {
swap(nums[i++], nums[j]);
}
}
swap(nums[i], nums[right]);
return i;
}
常见问题:
- 为什么选择最右元素作为基准?可以随机选择基准来避免最坏情况
- 如何优化递归深度?可以尾递归优化或使用迭代实现
- 处理大量重复元素时效率低?可使用三路快排
3. 二分查找算法
3.1 标准二分查找框架
二分查找看似简单,但边界条件容易出错。以下是可靠的实现模板:
cpp复制int binarySearch(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
3.2 二分查找变种问题
- 查找第一个等于target的元素:
cpp复制while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left < nums.size() && nums[left] == target ? left : -1;
- 查找最后一个等于target的元素:
cpp复制while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right >= 0 && nums[right] == target ? right : -1;
注意:二分查找的难点在于确定搜索区间和终止条件,建议通过画图理解区间变化。
4. 前缀和与差分技巧
4.1 前缀和应用
前缀和常用于快速计算区间和,预处理时间复杂度O(n),查询O(1):
cpp复制vector<int> prefixSum(nums.size() + 1, 0);
for (int i = 0; i < nums.size(); ++i) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
// 查询区间[i,j]的和
int sum = prefixSum[j + 1] - prefixSum[i];
典型例题:给定数组,求所有连续子数组的和等于k的个数。解法是利用前缀和+哈希表。
4.2 差分数组
差分是前缀和的逆操作,适用于区间更新、单点查询场景:
cpp复制vector<int> diff(nums.size(), 0);
diff[0] = nums[0];
for (int i = 1; i < nums.size(); ++i) {
diff[i] = nums[i] - nums[i - 1];
}
// 区间[i,j]增加val
void add(int i, int j, int val) {
diff[i] += val;
if (j + 1 < diff.size()) {
diff[j + 1] -= val;
}
}
// 还原数组
vector<int> result(diff.size(), 0);
result[0] = diff[0];
for (int i = 1; i < diff.size(); ++i) {
result[i] = result[i - 1] + diff[i];
}
应用场景:航班预订统计、会议室安排等问题。
5. 双指针技巧
5.1 同向双指针
典型问题:移除数组中指定元素
cpp复制int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); ++fast) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
5.2 相向双指针
典型问题:两数之和
cpp复制vector<int> twoSum(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return {left, right};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return {};
}
5.3 快慢指针
典型问题:判断链表是否有环
cpp复制bool hasCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
6. 综合例题解析
6.1 例题一:区间合并
给定一组区间,合并所有重叠的区间。
解法思路:
- 按区间起点排序
- 使用双指针遍历并合并重叠区间
cpp复制vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.empty()) return {};
sort(intervals.begin(), intervals.end());
vector<vector<int>> res;
res.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); ++i) {
if (res.back()[1] >= intervals[i][0]) {
res.back()[1] = max(res.back()[1], intervals[i][1]);
} else {
res.push_back(intervals[i]);
}
}
return res;
}
6.2 例题二:寻找重复数
给定包含n+1个整数的数组,数字在1到n之间,证明至少存在一个重复数。
解法思路:
- 将问题转化为链表环检测
- 使用快慢指针找到环入口
cpp复制int findDuplicate(vector<int>& nums) {
int slow = nums[0], fast = nums[0];
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = nums[0];
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
7. 算法优化技巧
7.1 排序优化
- 对于基本有序数据:使用插入排序
- 对于大量重复元素:三路快排
- 对于链表排序:归并排序
- 对于外部排序:多路归并
7.2 二分查找优化
- 对于模糊匹配:可以修改判断条件
- 对于无限数据流:指数后退法
- 对于精度问题:可以二分答案
7.3 前缀和与差分的高级应用
- 二维前缀和:用于矩阵区域求和
- 树状数组:动态维护前缀和
- 线段树:支持更复杂的区间操作
在实际编码面试中,我发现很多问题都是这些基础算法的组合或变种。建议先掌握每个算法的标准实现,再通过大量练习来培养识别问题模式的能力。对于排序算法,重点理解每种算法的适用场景;对于二分查找,要特别注意边界条件;前缀和与差分技巧在区间问题上非常高效;双指针则能优雅地解决许多线性结构的问题。
