按符号重排数组 图解题解
这道题到底在问什么
- 输入
- nums = [3,1,-2,-5,2,-4]
- 输出
- [3,-2,1,-5,2,-4]
- 输入
- nums = [-1,1]
- 输出
- [1,-1]
先想最直接的笨办法
记牢这一句:正数排偶数位、负数排奇数位,i 和 j 各管一路、每次前进两格。下面从数组第一个元素开始,一个一个分派。(动画第 3 步)
最优解:一步一步想明白
- 3记牢这一句:正数排偶数位、负数排奇数位,i 和 j 各管一路、每次前进两格。下面从数组第一个元素开始,一个一个分派。
- 4开局先把结果数组 ans 摆出来,六个位置现在都空着,用小点表示。两个写指针也就位了:i 停在偶数位 0,准备接正数;j 停在奇数位 1,准备接负数。它们各走各的、互不干扰。下面从 nums 的第一个元素读起。
- 5再把分工说清楚。ans 的偶数位 0、2、4 三个坑专留给正数,奇数位 1、3、5 三个坑专留给负数。因为下标 0 是偶数位,第一个填进去的正数就坐上了开头,正好满足以正数开头。正负各占一半坑,交替自然形成。
- 6紫色指针移到 nums[0],读到的值是 3。分派之前只问一件事:它是正还是负。3 是正数,大于 0,该走偶数位这一路,交给指针 i。
- 7把 nums[0] 标成绿色,提醒它是正数。正数这一路由 i 领着,i 现在指着偶数位 0,所以它的目标就是 ans[0]。右边面板用光束指向那个空槽。
- 8把 3 落进 ans[0],这个坑就填实了。正数收完一个,i 加二跳到下一个偶数位 2,等着接下一个正数。 nums[0] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 1 个数。
- 9紫色指针移到 nums[1],读到的值是 1。分派之前只问一件事:它是正还是负。1 是正数,大于 0,该走偶数位这一路,交给指针 i。
- 10把 nums[1] 标成绿色,提醒它是正数。正数这一路由 i 领着,i 现在指着偶数位 2,所以它的目标就是 ans[2]。右边面板用光束指向那个空槽。
- 11把 1 落进 ans[2],这个坑就填实了。正数收完一个,i 加二跳到下一个偶数位 4,等着接下一个正数。 nums[1] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 2 个数。
- 12紫色指针移到 nums[2],读到的值是 -2。分派之前只问一件事:它是正还是负。-2 是负数,小于 0,该走奇数位这一路,交给指针 j。
- 13把 nums[2] 标成红色,提醒它是负数。负数这一路由 j 领着,j 现在指着奇数位 1,所以它的目标就是 ans[1]。右边面板用光束指向那个空槽。
- 14把 -2 落进 ans[1],这个坑就填实了。负数收完一个,j 加二跳到下一个奇数位 3,等着接下一个负数。 nums[2] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 3 个数。
- 15紫色指针移到 nums[3],读到的值是 -5。分派之前只问一件事:它是正还是负。-5 是负数,小于 0,该走奇数位这一路,交给指针 j。
- 16把 nums[3] 标成红色,提醒它是负数。负数这一路由 j 领着,j 现在指着奇数位 3,所以它的目标就是 ans[3]。右边面板用光束指向那个空槽。
- 17把 -5 落进 ans[3],这个坑就填实了。负数收完一个,j 加二跳到下一个奇数位 5,等着接下一个负数。 nums[3] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 4 个数。
- 18紫色指针移到 nums[4],读到的值是 2。分派之前只问一件事:它是正还是负。2 是正数,大于 0,该走偶数位这一路,交给指针 i。
- 19把 nums[4] 标成绿色,提醒它是正数。正数这一路由 i 领着,i 现在指着偶数位 4,所以它的目标就是 ans[4]。右边面板用光束指向那个空槽。
- 20把 2 落进 ans[4],这个坑就填实了。正数收完一个,i 加二跳到下一个偶数位 6,等着接下一个正数。 nums[4] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 5 个数。
- 21紫色指针移到 nums[5],读到的值是 -4。分派之前只问一件事:它是正还是负。-4 是负数,小于 0,该走奇数位这一路,交给指针 j。
- 22把 nums[5] 标成红色,提醒它是负数。负数这一路由 j 领着,j 现在指着奇数位 5,所以它的目标就是 ans[5]。右边面板用光束指向那个空槽。
- 23把 -4 落进 ans[5],这个坑就填实了。负数收完一个,j 加二跳到下一个奇数位 7,等着接下一个负数。 nums[5] 变蓝,表示这一格处理完毕。目前 ans 里已经放了 6 个数。
- 24六个元素全部分派完毕。回头看一眼:偶数位 0、2、4 是 3、1、2,正是原来正数的先后;奇数位 1、3、5 是 -2、-5、-4,正是原来负数的先后。相邻两数一正一负交替,开头是正数,同号顺序原封不动。结果就是 [3,-2,1,-5,2,-4],和一开始记下的答案对上了。
⚠️ 容易写错的地方
✗ 错:把正数放奇数位、负数放偶数位
✓ 对:正数放偶数位 0、2、4,负数放奇数位 1、3、5
题目要求以正数开头,下标 0 是偶数位,第一个正数必须坐在这里,记反了开头就成了负数
✗ 错:i、j 每次只加一
✓ 对:i、j 每次加二,跳过异号的位置
偶数位彼此隔一个奇数位,加二才能落到下一个同号坑;加一会踩进异号槽、互相覆盖
✗ 错:先排序再交替
✓ 对:不排序,按原顺序遍历直接分派
题目要求同号元素保持原有先后,排序会打乱这个顺序
✗ 错:想在原数组上原地交换
✓ 对:另开结果数组按符号写入
原地交换很难同时保住正、负两组各自的相对顺序,题目也明说不需要原地修改
完整代码(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 rearrangeArray(self, nums: List[int]) -> List[int]:
ans = [0] * len(nums)
i, j = 0, 1
for x in nums:
if x > 0:
ans[i] = x
i += 2
else:
ans[j] = x
j += 2
return ansC++
#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> rearrangeArray(vector<int>& nums) {
vector<int> ans(nums.size());
int i = 0, j = 1;
for (int x : nums) {
if (x > 0) {
ans[i] = x;
i += 2;
} else {
ans[j] = x;
j += 2;
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int[] rearrangeArray(int[] nums) {
int[] ans = new int[nums.length];
int i = 0, j = 1;
for (int x : nums) {
if (x > 0) {
ans[i] = x;
i += 2;
} else {
ans[j] = x;
j += 2;
}
}
return ans;
}
}复杂度
时间
O(n)
n 是数组长度。只从头到尾扫一遍 nums,每个元素做一次符号判断和一次写入,都是常数操作,总量随 n 线性增长
空间
O(n)
按峰值算。题目允许不原地修改,这里额外开了一张与 nums 等长的结果数组 ans,占用 n 个位置;两个指针只是常数,峰值就是这张 ans,量级为 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 按符号重排数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心思路一句话是什么?+
另开一张等长的结果数组,用两个写指针分派:i 从偶数位 0 起收正数、j 从奇数位 1 起收负数,一遍遍历原数组,正数写 ans[i] 后 i 加二、负数写 ans[j] 后 j 加二。正数占偶数位、负数占奇数位,天然正负交替、正数开头,同号顺序也原封不动。
能不能做到原地、O(1) 额外空间?+
一般面试给 O(n) 的额外数组就够了,题目也明说不需要原地修改。真要原地并且保住正、负两组各自的相对顺序,会涉及复杂的循环换位,代价高、易错,不如开新数组直白。所以标准答法就是 O(n) 空间的一遍分派。
除了双写指针,还有别的写法吗?+
可以先扫一遍把正数、负数分别收进两个列表,再交替地从两个列表里取,拼成结果。思路一样、复杂度也是 O(n) 时间 O(n) 空间,只是多了一次收集和拼接;双写指针把这两步合成了一遍,更简洁。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 按符号重排数组 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。