按奇偶排序数组 II 图解题解
这道题到底在问什么
- 输入
- nums=[4,2,5,7]
- 输出
- [4,5,2,7] (偶位放偶数、奇位放奇数)
- 输入
- nums=[2,3]
- 输出
- [2,3] (本来就合规,原样返回)
最优解:为什么这么做
一句话答案:LeetCode 922 按奇偶排序数组 II 用双指针分轨:偶数归偶下标、奇数归奇下标,i 走偶位遇到奇数就和 j 走的奇位上那个偶数交换,一趟归位,时间 O(n)、空间 O(1)。
偶数进偶数位、奇数进奇数位,要摆成什么样
给一个数组 nums,题目保证里面恰好一半偶数、一半奇数。要重新排一下,让每个偶数落在偶数下标、每个奇数落在奇数下标,任意一种合规摆法都算对。题面 nums=[4,2,5,7] 可以摆成 [4,5,2,7]:偶位 0、2 上是 4、2,奇位 1、3 上是 5、7。另一个例子 nums=[2,3] 本来就合规,原样返回就行。
开个等大的新数组分着填,卡在哪
另开一个和 nums 等大的新数组,扫一遍原数组,偶数往新数组的下一个偶位放、奇数往下一个奇位放,两个写指针各管一条,一趟 O(n) 就摆好了。麻烦在于它多吃了一个数组的 O(n) 空间,而这题进阶要求不开额外数组、原地摆好,只准用 O(1) 额外空间。所以真正要解决的是:能不能就在 nums 上挪,把奇偶各归各位。
不开新数组,怎么就地把奇偶各归各位
开两条下标轨道:一条只走偶下标 0、2、4…,用 i 标记;一条只走奇下标 1、3、5…,用 j 标记,两条各跳 2 步、互不串道。i 停在某个偶位,上面正好是偶数就本就对、跳过;上面是奇数就说明放错了。因为偶数奇数各占一半,偶位上多出一个奇数,必然有个奇位占着一个本该在偶位的偶数,它俩正是一对错位。让 j 沿奇位往后找到那个放错的偶数,和 i 这里一换,两处同时归位。
i、j 各跑各的轨道,一次交换修好两格
落到代码:i 用 for 循环从 0 起、每次 +2 扫遍偶位;j 从 1 起走奇位。i 每到一个偶位,先看那里是不是奇数,是偶数直接过。是奇数,就让 j 往后走,跳过奇位上那些本就是奇数、没放错的格子(每次 +2),直到停在一个偶数上,交换这两格。要紧的是 j 全程不重置:这一轮停在哪,下一轮从哪接着往后走,因为它走过的奇位要么已对位、要么已被换好,没必要回头。
题面 [4,2,5,7] 走一遍,只在偶位 2 换一次
数组长 4,奇位指针 j 从 1 起。先看偶位 0,上面是 4,偶数配偶位、本就对,跳过。再看偶位 2,上面是 5,奇数放错了,得去奇位找偶数换。此时 j 指向奇位 1,那里是 2,正好是偶数,j 不必再往后走,直接换:偶位 2 和奇位 1 交换,数组变成 [4,5,2,7]。偶位只剩这两个,循环结束,返回 [4,5,2,7]:偶位 4、2 全偶数,奇位 5、7 全奇数,各归各位。
j 每轮从 1 重扫,一趟就退化成两趟平方
先算账:i 从头到尾走偶位、j 从头到尾走奇位,两个都只增不减,合起来不过把数组扫一遍,时间 O(n);全程在 nums 上原地交换,只多用 i、j 两个下标,空间 O(1),进阶要求的不开额外数组也就满足了。
几处一改就错:i、j 若写成每次 +1 逐格走,就会落到不属于自己那半的下标上,两条轨道立刻串了。j 若每轮从 1 重扫,前面已对位的格子被反复检查,一趟 O(n) 拖成 O(n²),接着上一轮往后走才对。交换对象也别找错:偶位放错时换的是奇位上放错的偶数,不是随手另一个奇数;至于 [2,3] 这种本就合规的,没有一个偶位是奇数,一次不换直接返回。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住这套「偶位放偶数、奇位放奇数,i 和 j 各跳 2」,下面每一帧都在套它。
- 4先看规则:下标是偶数的格子要放偶数,下标是奇数的格子要放奇数。现在这个数组还没摆好。
- 5放两个指针:偶位指针标 i 从 0 起,奇位指针标 r(就是代码里的 j)从 1 起。i 只落偶数下标、j 只落奇数下标,各跳 2 步。
- 6红色这 4 个都放错了:偶位 0 和 2 上是奇数 3、5,奇位 1 和 5 上是偶数 4、2。它们正好两两配对,一次交换能同时修好两处。
- 7i 跳到偶位 0,看看上面是什么:这里是 3。
- 83 是奇数,却站在偶位 0,放错了(变红)。要去奇位那边找一个放错的偶数来跟它换。
- 9j 到奇位 1,这里是 4,偶数却站在奇位,正是放错的,就用它跟偶位 0 交换。
- 10交换:偶位 0 拿到偶数 4,奇位 1 拿到奇数 3,两边同时归位(变绿)。
- 11i 跳到偶位 2,看看上面是什么:这里是 5。
- 125 是奇数,却站在偶位 2,放错了(变红)。要去奇位那边找一个放错的偶数来跟它换。
- 13j 在奇位 1,这里是 3,奇数待在奇位是对的,不能动,j 加 2 接着往后找。
- 14j 在奇位 3,这里是 7,奇数待在奇位是对的,不能动,j 加 2 接着往后找。
- 15j 到奇位 5,这里是 2,偶数却站在奇位,正是放错的,就用它跟偶位 2 交换。
- 16交换:偶位 2 拿到偶数 2,奇位 5 拿到奇数 5,两边同时归位(变绿)。
- 17i 跳到偶位 4,看看上面是什么:这里是 6。
- 186 是偶数,正好站在偶位 4,本来就对(变绿),跳过,i 接着往后走。
- 19i 跳到偶位 6,看看上面是什么:这里是 8。
- 208 是偶数,正好站在偶位 6,本来就对(变绿),跳过,i 接着往后走。
- 21i 把偶位 0、2、4、6 都看过了,循环结束。下面逐位验收一下结果。
- 22先看偶位:0、2、4、6 上分别是 4、2、6、8,全是偶数,符合要求。
- 23再看奇位:1、3、5、7 上分别是 3、7、5、1,全是奇数,也符合要求。
- 24整个数组摆好了:偶位放偶数、奇位放奇数。答案就是 [4,3,2,7,6,5,8,1]。
⚠️ 容易写错的地方
✗ 错:i、j 都只 +1 逐个走
✓ 对:i、j 各 +2,只落在各自那半的下标
偶位指针只能停偶下标、奇位指针只能停奇下标,跳 2 才对得上奇偶
✗ 错:每轮让 j 都从 1 重新扫
✓ 对:j 不重置,跨轮接着往后走
重置会退化成 O(n²),还会重复检查已经对位的元素
✗ 错:偶位放错时去找另一个奇数换
✓ 对:要找奇位上放错的偶数
偶位的奇数 ⇄ 奇位的偶数,一次交换两处同时归位
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def sortArrayByParityII(self, nums: List[int]) -> List[int]:
n, j = len(nums), 1
for i in range(0, n, 2):
if nums[i] % 2:
while nums[j] % 2:
j += 2
nums[i], nums[j] = nums[j], nums[i]
return numsC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
for (int i = 0, j = 1; i < nums.size(); i += 2) {
if (nums[i] % 2) {
while (nums[j] % 2) {
j += 2;
}
swap(nums[i], nums[j]);
}
}
return nums;
}
};Java
import java.util.*;
class Solution {
public int[] sortArrayByParityII(int[] nums) {
for (int i = 0, j = 1; i < nums.length; i += 2) {
if (nums[i] % 2 == 1) {
while (nums[j] % 2 == 1) {
j += 2;
}
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
return nums;
}
}复杂度
时间
O(n)
i 和 j 各自只向前走一遍,合计扫一遍数组
空间
O(1)
原地交换,只用 i、j 两个下标变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 按奇偶排序数组 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题和「按奇偶排序数组」(LeetCode 905)是一回事吗?+
不是,差在对下标的要求。905 只要求所有偶数排在所有奇数前面,具体谁在下标 0、谁在下标 1 无所谓,所以能用对撞双指针:左指针从头找奇数、右指针从尾找偶数,遇到「左奇右偶」就换,向中间收。922 严格得多,要偶数下标放偶数、奇数下标放奇数,是值的奇偶和位置的奇偶对齐,所以改成分两条轨道走——i 管偶位、j 管奇位,各自找放错的来换。一句话:905 管前后分组,922 管奇偶对位。
为什么交换来交换去,不会把已经摆好的位置弄乱?+
因为 i 只增不减地走偶位、j 也只增不减地走奇位,两者都不回头。每次 i 触发交换,被换走的是偶位上放错的奇数,换来的是奇位上放错的偶数,两边都从「错」变「对」;而 j 走过、确认对位的奇位,后面不会再退回去碰它,已经归位的元素也就没机会被动。所以每一次交换只会让摆对的格子变多,不会回退。
进阶要求不用额外空间,这个解满足吗?+
满足。整个过程都在原数组 nums 上原地交换,只用了 i、j 两个下标变量,额外空间是常数 O(1)。相比之下,开一个新数组、用两个写指针分别往偶位奇位填的写法思路更直白,但要多花 O(n) 空间;本解省掉了那个数组,正好卡在进阶要求上。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 按奇偶排序数组 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。