Skip to content

数组、前缀和与区间 ​

数组题先确认输入范围、是否有序、是否允许修改原数组,以及结果是否可能溢出。重点是写清循环不变量、边界与复杂度;需要特定标准库 API 时,跳转到对应的算法或 C++ 笔记。

数组与双指针 ​

双指针的重点是先说明两个指针分别代表什么,以及哪条不变量决定指针移动:

  • 相向双指针:常用于有序数组两数之和、盛水容器和回文判断;根据当前和或高度移动一端;
  • 同向双指针:读写指针压缩数组,或维护快慢位置;
  • 滑动窗口:维护区间 [left, right] 及统计量,右端扩张,条件不满足或需要优化答案时收缩左端。

滑动窗口成立通常依赖可维护的单调性。例如正整数数组中“和至少为 target 的最短连续子数组”可用 O(n) 窗口;若数组含负数,窗口和不再随右端扩张单调增长,不能直接套模板,可能需要前缀和与单调队列。

原地压缩:移动零 ​

使用读指针扫描、写指针写入非零元素,再把尾部补零,可保持非零元素相对顺序:

cpp
void move_zeroes(std::vector<int>& nums) {
    std::size_t write = 0;
    for (int value : nums) {
        if (value != 0) nums[write++] = value;
    }
    std::fill(nums.begin() + write, nums.end(), 0);
}

时间 O(n)、额外空间 O(1)。

前缀和与差分 ​

前缀和适用于频繁查询静态区间和:

text
prefix[0] = 0
prefix[i + 1] = prefix[i] + nums[i]
sum(l, r) = prefix[r + 1] - prefix[l]

差分数组适用于大量区间加法、最后统一还原。对闭区间 [left, right] 加 value:

cpp
diff[left] += value;
if (right + 1 < n) diff[right + 1] -= value;

随后对 diff 做一次前缀和即可得到每个位置的最终值。航班预订统计和时间段并发计数都是该模式;时间边界是否“登出时仍在线”必须先定义清楚。

区间题 ​

区间题通常先排序,再维护已选择/合并区间的右边界:

  • 合并区间:按左端点升序,重叠时扩展当前右端点;
  • 最多不重叠区间:按右端点升序,尽早结束以给后续保留空间;
  • 最少箭数:按端点排序后维护当前公共交集的最小右端点。

比较器应使用 const 引用并满足严格弱序:

cpp
std::sort(intervals.begin(), intervals.end(),
          [](const auto& a, const auto& b) {
              return a[0] != b[0] ? a[0] < b[0] : a[1] < b[1];
          });

复杂度速查 ​

操作典型复杂度
遍历、双指针、前缀和构建O(n)
差分更新后统一还原O(n)
已排序序列二分O(log n)
区间排序 + 单次扫描O(n log n)

关联笔记 ​

使用 Markdown 与 VitePress 构建