题目描述
思路解析
一句话答案:LeetCode 1470 重新排列数组:nums 前一半是 x 组、后一半是 y 组,把配对的 nums[i] 和 nums[i+n] 交替摆成 x1,y1,x2,y2,i 扫一遍即可,时间 O(n)、空间 O(n)。
重排成 x1,y1,x2,y2,要交出什么
给你一个长度是 2n 的数组 nums,它的前一半 [x1,x2,…,xn] 是 x 组、后一半 [y1,y2,…,yn] 是 y 组,两组一样长。要返回的是把每一对 x、y 拆开、交替摆好的新数组 [x1,y1,x2,y2,…,xn,yn]。题面例子 nums=[1,2,3,4]、n=2,前两个 1、2 是 x 组,后两个 3、4 是 y 组,交出的答案是 [1,3,2,4]。
直接拿相邻的两个数当一对,为什么全乱
最容易踩的坑,是把 nums 当成已经排成 [x1,y1,x2,y2] 的样子,顺手抓相邻的 nums[0]、nums[1] 当第一对。可题目明说前一半全是 x、后一半全是 y,两组各自连续排着,nums[0] 和 nums[1] 其实是 x 组里挨着的两个 x,根本不是一对。拿相邻取数,会把同组的两个数错配成一对,拼出来整个乱套。
配对的两个数,固定相隔 n
把布局摆正就清楚了:x 组第 i 个待在下标 i,往后要跨过整整 n 个 x 才轮到 y 组,所以和它配对的 y 组第 i 个就落在下标 i+n。这条相隔 n 是整道题的钥匙——nums[i] 和 nums[i+n] 才是一对。放到答案里,x_i 排在第 2i 位、它的搭档 y_i 紧跟在第 2i+1 位,两个数一前一后贴着摆。
一个 i 从头扫到尾,交替填答案
认准相隔 n 之后,过程就一条直线:让 i 从 0 走到 n−1,每一步取出 nums[i] 和 nums[i+n] 这一对,先把 nums[i] 写进答案第 2i 位、再把 nums[i+n] 写进第 2i+1 位。n 对全部走完,答案的 2n 个位置正好按 x1,y1,x2,y2 的节奏填满。参考代码用切片把前后两半切开、zip 配成对后铺平,做的就是这件事。
把 [1,2,3,4] 配 n=2 走一遍
nums=[1,2,3,4]、n=2,前两个 1、2 是 x 组,后两个 3、4 是 y 组。先看第一对:取 nums[0]=1 放进答案第 0 位,再取它的搭档 nums[0+2]=nums[2]=3 放进第 1 位,答案现在是 [1,3]。轮到第二对:取 nums[1]=2 放进第 2 位,取搭档 nums[1+2]=nums[3]=4 放进第 3 位,答案变成 [1,3,2,4]。两对走完,正好是题面要的输出。
下标偏移写歪,答案会整体错位
最容易写歪的是搭档那一步:搭档下标是 i+n、落点是答案第 2i+1 位,偏移记成 i+n+1、或者把落点算成 2i,会让整串数整体错位、还可能越界。用 Java 预先开数组时,长度要开成 2n 而不是 n——有的写法把 2n 记作 n<<1,也就是把 n 左移一位当乘 2 用,开成 n 会装不下。时间上扫 n 对、每对两次常数操作,是 O(n);答案要装下全部 2n 个数,空间 O(n)。边界也稳:n=1 时只有一对,输出和输入长得一样;数组里有重复值、甚至全相等,也照样按位置交替,排完看着像没动、其实顺序已按规则重排。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记牢一句:配对的两个数相隔 n,nums[i] 落答案第 2i 位、nums[i+n] 落第 2i+1 位,i 扫一遍就交替拼好。下面每帧都在套这句。
总览 · 源数组分两组 + 空答案:先看清布局。上面是源数组 nums = [3,8,1,6,9,4,7,2,5,10],下标 0 到 4 这前 5 个是 x 组,下标 5 到 9 这后 5 个是 y 组。配成一对的两个数相隔 5:比如 x 组的 3 在下标 0,它的搭档在下标 5,就是 4。右边是空的答案数组 ans,我们要按 x1,y1,x2,y2 的顺序一位一位往里填。先从答案的第 0 位开始。
第 0 位 · 读 nums[0] = 3:答案第 0 位该放 x 组第 1 个。紫色指针落在源数组下标 0,值是 3。它就是这一对里的 x,稍后它的搭档 nums[5] 会紧跟着补到它后面。
第 0 位 · 放入 3:把 3 写进答案的第 0 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3]。已经排好 1 个,接着补它的搭档 y。
第 1 位 · 读 nums[5] = 4:答案第 1 位该放 y 组第 1 个。紫色指针跳到源数组下标 5,也就是上一个 x 往后数 5 格的位置,值是 4。它是刚才那个 x 的搭档,要紧挨着补在它后面。
第 1 位 · 放入 4:把 4 写进答案的第 1 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4]。已经排好 2 个,这一对凑齐了,换下一对的 x。
第 2 位 · 读 nums[1] = 8:答案第 2 位该放 x 组第 2 个。紫色指针落在源数组下标 1,值是 8。它就是这一对里的 x,稍后它的搭档 nums[6] 会紧跟着补到它后面。
第 2 位 · 放入 8:把 8 写进答案的第 2 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8]。已经排好 3 个,接着补它的搭档 y。
第 3 位 · 读 nums[6] = 7:答案第 3 位该放 y 组第 2 个。紫色指针跳到源数组下标 6,也就是上一个 x 往后数 5 格的位置,值是 7。它是刚才那个 x 的搭档,要紧挨着补在它后面。
第 3 位 · 放入 7:把 7 写进答案的第 3 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7]。已经排好 4 个,这一对凑齐了,换下一对的 x。
第 4 位 · 读 nums[2] = 1:答案第 4 位该放 x 组第 3 个。紫色指针落在源数组下标 2,值是 1。它就是这一对里的 x,稍后它的搭档 nums[7] 会紧跟着补到它后面。
第 4 位 · 放入 1:把 1 写进答案的第 4 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1]。已经排好 5 个,接着补它的搭档 y。
第 5 位 · 读 nums[7] = 2:答案第 5 位该放 y 组第 3 个。紫色指针跳到源数组下标 7,也就是上一个 x 往后数 5 格的位置,值是 2。它是刚才那个 x 的搭档,要紧挨着补在它后面。
第 5 位 · 放入 2:把 2 写进答案的第 5 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1,2]。已经排好 6 个,这一对凑齐了,换下一对的 x。
第 6 位 · 读 nums[3] = 6:答案第 6 位该放 x 组第 4 个。紫色指针落在源数组下标 3,值是 6。它就是这一对里的 x,稍后它的搭档 nums[8] 会紧跟着补到它后面。
第 6 位 · 放入 6:把 6 写进答案的第 6 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1,2,6]。已经排好 7 个,接着补它的搭档 y。
第 7 位 · 读 nums[8] = 5:答案第 7 位该放 y 组第 4 个。紫色指针跳到源数组下标 8,也就是上一个 x 往后数 5 格的位置,值是 5。它是刚才那个 x 的搭档,要紧挨着补在它后面。
第 7 位 · 放入 5:把 5 写进答案的第 7 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1,2,6,5]。已经排好 8 个,这一对凑齐了,换下一对的 x。
第 8 位 · 读 nums[4] = 9:答案第 8 位该放 x 组第 5 个。紫色指针落在源数组下标 4,值是 9。它就是这一对里的 x,稍后它的搭档 nums[9] 会紧跟着补到它后面。
第 8 位 · 放入 9:把 9 写进答案的第 8 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1,2,6,5,9]。已经排好 9 个,接着补它的搭档 y。
第 9 位 · 读 nums[9] = 10:答案第 9 位该放 y 组第 5 个。紫色指针跳到源数组下标 9,也就是上一个 x 往后数 5 格的位置,值是 10。它是刚才那个 x 的搭档,要紧挨着补在它后面。
第 9 位 · 放入 10:把 10 写进答案的第 9 位,绿色标出它在源数组里已经被取走。右边 ans 现在是 [3,4,8,7,1,2,6,5,9,10]。已经排好 10 个,全部排完了。
完成 · ans = [3,4,8,7,1,2,6,5,9,10]:十个位置都填满了,回看一遍:答案是按 x1,y1,x2,y2 一直到 x5,y5 的节奏拼出来的。x 组的 3、8、1、6、9 落在偶数位 0、2、4、6、8,y 组的 4、7、2、5、10 落在奇数位 1、3、5、7、9,两组交替咬合。最终 ans 就是 [3,4,8,7,1,2,6,5,9,10],跟开头说的一字不差。整个过程就是认准配对相隔 5,一对一对地交替摆,没有任何花招。
边界:n=1 只有一对、输出同输入;有重复值照样按位置交替;全相等时排完看着不变。
面试重点:搭档相隔 n 源于前后分组;进阶可借值小于 1024 编码压位做到 O(1) 额外空间;三语言差别在切片/push/定长写指针。
参考代码
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 shuffle(self, nums: List[int], n: int) -> List[int]: return [x for pair in zip(nums[:n], nums[n:]) for x in pair]复杂度
- 时间:O(n),n 是配对的对数(数组长度的一半)。从 i = 0 扫到 n-1,每对做两次常数操作把两个数放进答案,总共 2n 次常数操作,所以是线性的 O(n)。本题 n 最大 500,毫无压力
- 空间:O(n),按峰值算,答案数组要装下全部 2n 个元素,这是必须交出去的输出,规模 O(n)。若不把返回结果计入,只用了循环变量和写指针等常数个空间,即 O(1) 额外空间。Python 版多切出两个临时半数组,也是 O(n) 量级,不改变结论
易错点
面试追问把动画讲成自己的话
追问为什么搭档的下标是 i+n,这个 n 是怎么来的?
追问这题能不能做到不开新数组、只用 O(1) 额外空间?
追问三种语言实现上有什么差别?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
一维数组的动态和
LeetCode 1480 · 简单 · 沿着 数组套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题