题目描述
思路解析
一句话答案: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] 这种本就合规的,没有一个偶位是奇数,一次不换直接返回。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这套「偶位放偶数、奇位放奇数,i 和 j 各跳 2」,下面每一帧都在套它。
先看规则:下标是偶数的格子要放偶数,下标是奇数的格子要放奇数。现在这个数组还没摆好。
放两个指针:偶位指针标 i 从 0 起,奇位指针标 r(就是代码里的 j)从 1 起。i 只落偶数下标、j 只落奇数下标,各跳 2 步。
红色这 4 个都放错了:偶位 0 和 2 上是奇数 3、5,奇位 1 和 5 上是偶数 4、2。它们正好两两配对,一次交换能同时修好两处。
i 跳到偶位 0,看看上面是什么:这里是 3。
3 是奇数,却站在偶位 0,放错了(变红)。要去奇位那边找一个放错的偶数来跟它换。
j 到奇位 1,这里是 4,偶数却站在奇位,正是放错的,就用它跟偶位 0 交换。
交换:偶位 0 拿到偶数 4,奇位 1 拿到奇数 3,两边同时归位(变绿)。
i 跳到偶位 2,看看上面是什么:这里是 5。
5 是奇数,却站在偶位 2,放错了(变红)。要去奇位那边找一个放错的偶数来跟它换。
j 在奇位 1,这里是 3,奇数待在奇位是对的,不能动,j 加 2 接着往后找。
j 在奇位 3,这里是 7,奇数待在奇位是对的,不能动,j 加 2 接着往后找。
j 到奇位 5,这里是 2,偶数却站在奇位,正是放错的,就用它跟偶位 2 交换。
交换:偶位 2 拿到偶数 2,奇位 5 拿到奇数 5,两边同时归位(变绿)。
i 跳到偶位 4,看看上面是什么:这里是 6。
6 是偶数,正好站在偶位 4,本来就对(变绿),跳过,i 接着往后走。
i 跳到偶位 6,看看上面是什么:这里是 8。
8 是偶数,正好站在偶位 6,本来就对(变绿),跳过,i 接着往后走。
i 把偶位 0、2、4、6 都看过了,循环结束。下面逐位验收一下结果。
先看偶位:0、2、4、6 上分别是 4、2、6、8,全是偶数,符合要求。
再看奇位:1、3、5、7 上分别是 3、7、5、1,全是奇数,也符合要求。
整个数组摆好了:偶位放偶数、奇位放奇数。答案就是 [4,3,2,7,6,5,8,1]。
边界想清:本就合规直接过、一对错位换一次、最坏也只一趟。
两个高频追问:不回头保证不破坏、原地交换满足 O(1) 空间。
参考代码
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 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 nums复杂度
- 时间:O(n),i 和 j 各自只向前走一遍,合计扫一遍数组
- 空间:O(1),原地交换,只用 i、j 两个下标变量
易错点
面试追问把动画讲成自己的话
追问为什么这样交换一定不会破坏已经摆好的位置?
追问进阶要求不用额外空间,这个解满足吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
增减字符串匹配
LeetCode 942 · 简单 · 沿着 双指针套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题