双指针原地重排奇偶数时间o(n)空间o(1),左指针找偶数、右指针找奇数,交换后移动;需用(arr[i] & 1)判断奇偶以正确处理负数,避免%2==1的陷阱。

用双指针原地重排,时间 O(n)、空间 O(1)
直接遍历两次(先存奇数再存偶数)会额外开数组,浪费空间;而双指针法只需一次扫描、原地交换,是标准解法。核心思路:左指针从头找偶数,右指针从尾找奇数,找到就交换。
注意:std::vector 和原生数组都适用,但索引越界检查必须自己做;std::partition 虽简洁,但语义是“满足谓词的放前面”,不保证奇偶内部顺序,且底层也是双指针。
- 左指针
i从0开始,跳过奇数(arr[i] % 2 != 0),停在第一个偶数上 - 右指针
j从n-1开始,跳过偶数(arr[j] % 2 == 0),停在最后一个奇数上 - 当
i 时交换 <code>arr[i]和arr[j],然后各自移动 - 循环结束后,
[0, i)是奇数段,[i, n)是偶数段(i是第一个偶数下标)
小心负数取余的符号问题
C++ 中负数取余结果符号依赖于被除数(如 -3 % 2 == -1),直接用 % 2 == 1 判断奇数会漏掉负奇数。正确做法是用 abs(arr[i]) % 2 == 1 或更高效地用位运算 (arr[i] & 1) != 0——因为奇数最低位恒为 1,无论正负。
- 错误写法:
if (arr[i] % 2 == 1)→-3不满足 - 推荐写法:
if ((arr[i] & 1) != 0)→ 正负奇数都成立,无分支、无函数调用 - 若需兼容非整数类型(如
long long),仍可用& 1,它比abs()更快且无溢出风险
std::partition 怎么用才不踩坑
如果不想手写双指针,std::partition 是标准库最接近需求的工具,但它默认破坏原始顺序(不稳定),且要求传入迭代器范围和谓词。
- 正确调用:
std::partition(arr, arr + n, [](int x) { return (x & 1) != 0; }); - 常见错误:写成
std::partition(arr, arr + n, [](int x) { return x % 2 == 1; })→ 负数失效 - 性能提示:它内部也是双指针,但多了函数对象调用开销;若需稳定分区(保持奇数/偶数内部相对顺序),得用
std::stable_partition,但时间复杂度升为 O(n log n) - 注意:返回值是第一个偶数的迭代器,可用来分割或验证,比如
auto mid = std::partition(...); assert(std::all_of(arr, mid, [](int x){return x&1;}));
边界情况必须手动测试
空数组、全奇数、全偶数、单元素——这些情况双指针和 std::partition 都能处理,但容易因循环条件写错导致越界或死循环。
- 空数组:
n == 0时,双指针循环条件i 自然不触发,安全 - 全奇数:
i一路走到n,j停在n-1,最终i > j退出,没问题 - 关键陷阱:循环内没更新
i或j(比如忘记i++/j--),或条件写成i 导致交换后 <code>i == j时多换一次(把同一个数跟自己换,虽不报错但逻辑冗余) - 建议:所有指针移动必须放在每次成功跳过或交换之后,且优先用
而非 <code> 控制循环
std::partition 简洁但得盯住谓词和稳定性需求。最容易被忽略的是负数判断方式——用 & 1 几乎零成本,却能避开一整类运行时错误。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











