题目描述
思路解析动画文字版
记住这套「左偶就 i++、右奇就 j--、左奇右偶就交换」,下面每一帧都在套它。
双指针就位:i(左指针)站在下标 0,j(右指针)站在下标 7。中间紫色窗口是还没分好的区域。我们要让窗口里的偶数都流到左边、奇数都流到右边。
先看左指针。nums[0] = 3 是奇数,奇数该去右边,这一位站错了,先记着它,再去看右指针。
再看右指针。nums[7] = 7 也是奇数,而奇数本来就该待在右边,这一位其实是站对的。
左边奇数虽然站错,但右边是个已经站对的奇数,没法跟它换。所以先把右指针 j 往左收一格,把这个奇数定下来。
j 退到下标 6。下标 7 的 7 归入右边的奇数区(蓝色)。左指针那个奇数下一轮再处理。
先看左指针。nums[0] = 3 是奇数,按规矩它该去右边,是个站错位的。
再看右指针。nums[6] = 8 是偶数,按规矩它该来左边,也是个站错位的。两个刚好反着。
这是最划算的一步:左边的奇数和右边的偶数一交换,两个就都各就各位了。准备交换。
换完:下标 0 变成偶数 8,下标 6 变成奇数 3,都站对了。于是 i 右移到 1、j 左移到 5,窗口同时缩小两头。
先看左指针。nums[1] = 2 是偶数,偶数本来就该待在左边,这一位已经站对了。
既然左指针指的是偶数、位置正确,就不用动它,直接让 i 往右迈一步,去检查下一个。
i 走到下标 2。下标 1 的 2 正式归入左边的偶数区(绿色)。继续看新的左指针。
先看左指针。nums[2] = 1 是奇数,奇数该去右边,这一位站错了,先记着它,再去看右指针。
再看右指针。nums[5] = 5 也是奇数,而奇数本来就该待在右边,这一位其实是站对的。
左边奇数虽然站错,但右边是个已经站对的奇数,没法跟它换。所以先把右指针 j 往左收一格,把这个奇数定下来。
j 退到下标 4。下标 5 的 5 归入右边的奇数区(蓝色)。左指针那个奇数下一轮再处理。
先看左指针。nums[2] = 1 是奇数,按规矩它该去右边,是个站错位的。
再看右指针。nums[4] = 6 是偶数,按规矩它该来左边,也是个站错位的。两个刚好反着。
这是最划算的一步:左边的奇数和右边的偶数一交换,两个就都各就各位了。准备交换。
换完:下标 2 变成偶数 6,下标 4 变成奇数 1,都站对了。于是 i 右移到 3、j 左移到 3,窗口同时缩小两头。
两个指针在下标 3 撞上了,中间再没有未处理的元素,循环停止。此刻左边四个全是偶数、右边四个全是奇数。
分区完成:绿色 8、2、6、4 都是偶数,蓝色 1、5、3、7 都是奇数。偶在前、奇在后,这就是一个合法答案。
边界先想清:单元素、全偶、全奇,三种都不会发生交换,原样返回即可。
两个高频追问:一是和快排 partition 同源,二是稳定性与空间的取舍。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def sortArrayByParity(self, nums: List[int]) -> List[int]: i, j = 0, len(nums) - 1 while i < j: if nums[i] % 2 == 0: i += 1 elif nums[j] % 2 == 1: j -= 1 else: nums[i], nums[j] = nums[j], nums[i] i, j = i + 1, j - 1 return nums复杂度
- 时间:O(n),i 与 j 合起来只把数组扫过一遍
- 空间:O(1),原地交换,只用 i、j 两个下标
易错点
面试追问把动画讲成自己的话
追问这题和「快速排序的分区(partition)」有什么关系?
追问如果还要求偶数之间保持原有相对顺序(稳定)怎么办?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
按奇偶排序数组 II
LeetCode 922 · 简单 · 沿着 双指针套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题