双指针原地交换实现奇偶分离,时间O(n)、空间O(1);左指针找奇数、右指针找偶数,找到即交换,直至left>=right停止,避免越界或重复。

用双指针原地交换,时间O(n)、空间O(1)
直接在原数组上操作最高效,不需要额外容器。核心思路是:左指针从头找奇数,右指针从尾找偶数,找到就交换。这样所有偶数会逐渐“冒泡”到前面。
注意边界条件和循环终止逻辑——当 left >= right 时必须停止,否则可能越界或重复交换。
- 初始化
left = 0,right = n - 1 - 外层用
while (left 控制整体流程 - 内层:左指针跳过偶数(
arr[left] % 2 == 0),停在奇数上;右指针跳过奇数(arr[right] % 2 == 1),停在偶数上 - 交换后
left++且right--,继续下一轮
void moveEvenToFront(int arr[], int n) {
int left = 0, right = n - 1;
while (left < right) {
while (left < right && arr[left] % 2 == 0) left++;
while (left < right && arr[right] % 2 == 1) right--;
if (left < right) {
std::swap(arr[left], arr[right]);
left++;
right--;
}
}
}std::partition 能一行解决,但语义更清晰
如果你不强求手写双指针,std::partition 是标准库中专为这类“按谓词分组”设计的算法,它保证满足条件的元素全在前半段,且保持相对顺序(稳定分区)。
注意它返回的是第一个不满足谓词的位置,不是布尔值;而且它只接受迭代器,不能直接传数组名。
立即学习“C++免费学习笔记(深入)”;
- 谓词要写成
[](int x) { return x % 2 == 0; },表示“把偶数留下” - 数组需转为迭代器:
std::partition(arr, arr + n, ...) - 不保证偶数间/奇数间的原始顺序,但比手写更少出错
std::partition(arr, arr + n, [](int x) { return x % 2 == 0; });别用 vector::erase + insert,性能灾难
有人想遍历数组,遇到偶数就 erase 再 insert 到开头——这会导致大量内存搬移。每次 insert 在头部,平均 O(n),整体会退化到 O(n²)。
即使用 std::deque,插入头部是 O(1),但随机访问变慢,且破坏了原数组上下文(比如你本来就有 C 风格接口要传 int*)。
- 避免在循环中对
vector做头部插入/删除 - 如果必须用容器,先遍历分离偶奇,再拼接:
std::vector<int> evens, odds;</int>,最后用insert合并——这是可读性与性能的合理折中 - 但原题明确说“数组”,优先考虑原地操作
负数取余要注意符号,用 abs 或位运算更安全
C++ 中负数取余结果符号依赖实现(C++11 后规定向零取整,但 -3 % 2 仍是 -1),所以 arr[i] % 2 == 0 对负偶数(如 -4)仍成立,但有人误以为会出错。
更稳妥的方式是用位运算判断最低位:(arr[i] & 1) == 0,它对正负数都有效,且编译器通常优化得更好。
- 不要写
abs(arr[i]) % 2 == 0,多一次函数调用和取绝对值开销 -
arr[i] & 1是最轻量的奇偶判断,现代 CPU 上就是一条指令 - 如果业务逻辑明确数组非负,用
% 2完全没问题
实际写的时候最容易卡在双指针的“跳过”条件写反,或者忘记 left 的二次检查——交换前不判断,<code>left 和 right 可能已经相等甚至交叉。


















