算法方法
总导航:算法与开发
这里记录解题时最常用的策略判断。二分、贪心和枚举现在统一在一个主笔记中,方便从问题特征直接选择方法;它们的适用条件和推理方式不同,因此以下章节仍然分开说明。
先判断问题,再选择算法
不要从“我想套哪个模板”开始,而要先写出候选空间、约束和可验证的性质:
| 问题特征 | 首选方法 | 必须先确认的条件 | 不能直接套用的情况 |
|---|---|---|---|
| 答案空间有序,判定结果随答案单调变化 | 二分查找 | 单调性、边界和判定函数 | 只能比较局部大小、但没有全局单调性 |
| 候选规模小,可以完整遍历 | 枚举 | 候选数和单次计算复杂度 | 候选空间指数级增长且没有剪枝 |
| 每一步都有可证明的局部最优选择 | 贪心 | 交换论证、拟阵性质或其他正确性证明 | 早期选择可能影响后续可行性 |
先用可证明的基线得到正确答案,再考虑优化;优化必须保留边界样例和复杂度说明。
1. 二分查找:先证明单调性
二分适合在有序、单调或可判定的答案空间中不断缩小区间。使用前先明确:区间边界、判定函数的单调方向,以及循环结束时 left/right 的含义。若题目数据无序,先排序再查找;排序复杂度为 \(O(n\log n)\),单次二分为 \(O(\log n)\)。
使用 C 的排序函数时,可以先用 qsort 准备有序数据:
#include <stdlib.h>
static int cmpInt(const void* a, const void* b) {
int x = *(const int*)a;
int y = *(const int*)b;
return (x > y) - (x < y); // 避免使用 x - y 导致溢出
}
qsort(nums, numsSize, sizeof(int), cmpInt);
比较函数约定:compar(a,b) < 0 时 a 在 b 前,> 0 时 a 在 b 后,等于 0 时顺序不作要求。不要用 x - y 直接比较有符号整数,可能溢出。abs 用于取绝对值,使用前需包含 <stdlib.h>。
2. 枚举与贪心:区分“可遍历”与“可证明”
枚举是把候选状态按规则逐一试遍;例如 1423 题可以枚举从左端取多少个元素,右端数量由总数决定,再计算每一种取法的总和。若候选规模为 \(k\),直接枚举通常是 \(O(k)\),适合先作为正确性基线。
贪心则是在每一步选择当前看起来最优的分支。只有当局部最优能证明导向全局最优,并且选择不会破坏后续可行性时才可使用。若第一步无法证明,或后续选择可能推翻前面的判断,就应改用枚举、动态规划或其他可证明的方法。
1423 题的练习结论:枚举左端取值即可覆盖全部候选,右端取值由总长度确定;逐一计算区间和可以作为正确性基线。贪心方案无法在第一步证明局部最优会导向全局最优,因此不采用该方案。
3. 选型顺序
- subarray(连续):优先考虑滑动窗口、前缀和或双指针。
- subsequence(不连续):常见组合是排序 + 贪心,也可能需要前缀和或二分。
- 先检查单调性和可证明的局部最优;没有证明时,保留枚举作为基线,再比较复杂度和边界条件。
验证与复盘
每个新方法至少保留三类测试:最小输入、边界输入和会触发分支切换的输入。记录“为什么成立”与“在哪些条件下失效”,不要只保留一组通过样例。这样枚举可以作为正确性基线,二分可以核对边界收缩,贪心则必须能回溯到证明假设。