力扣-1470. 重新排列数组
1. 题目
题目地址(1470. 重新排列数组 - 力扣(LeetCode))
https://leetcode.cn/problems/shuffle-the-array/?envType=study-plan-v2&envId=primers-list
题目描述
给你一个数组 nums
,数组中有 2n
个元素,按 [x1,x2,...,xn,y1,y2,...,yn]
的格式排列。
请你将数组按 [x1,y1,x2,y2,...,xn,yn]
格式重新排列,返回重排后的数组。
示例 1:
输入:nums = [2,5,1,3,4,7], n = 3 输出:[2,3,5,4,1,7] 解释:由于 x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 ,所以答案为 [2,3,5,4,1,7]
示例 2:
输入:nums = [1,2,3,4,4,3,2,1], n = 4 输出:[1,4,2,3,3,2,4,1]
示例 3:
输入:nums = [1,1,2,2], n = 2 输出:[1,2,1,2]
提示:
1 <= n <= 500
nums.length == 2n
1 <= nums[i] <= 10^3
2.题解
2.1 额外数组 + 一次遍历
思路
代码
1. 双指针 + 标记
class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
vector<int> ans;
int i = 0, j = n, cnt = 0;
while(j != 2*n){
if(!cnt){
ans.push_back(nums[i++]);
cnt = (cnt+1) % 2;
} else{
ans.push_back(nums[j++]);
cnt = (cnt+1) % 2;
}
}
return ans;
}
};
2.下标覆盖(优化)
这里只使用一个i, 但是我们可以通过它们之间下标的关联关系实现映射关系
class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
vector<int> ans(2 * n);
for (int i = 0; i < n; i++) {
ans[2 * i] = nums[i];
ans[2 * i + 1] = nums[i + n];
}
return ans;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(n)\)
2.2 原地修改(标记 + 还原)
思路1-高位存储
一个int类型32位, 也就是2^32 - 1, 这里 nums[i] <= 1000 < 2^10 - 1, 所以我们只需要往后再利用十位存储新数据即可, 同时不影响原数组的使用(我只取低十位 & 1023),
这里的标记也十分的好还原, 直接将数组整体向右 >> 10 即可还原
代码
class Solution {
public int[] shuffle(int[] nums, int n) {
for (int i = 0; i < n; i++) {
// 存储数组新数据
nums[i * 2] |= (nums[i] & 1023) << 10;
nums[i * 2 + 1] |= (nums[i + n] & 1023) << 10;
}
// 还原数组
for (int i = 0; i < n * 2; i++) {
nums[i] >>= 10;
}
return nums;
}
}
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(1)\)
思路2-负数标记法
对于 [2,5,1,3,4,7] n = 3
我们想要将下标 0,1,2 -> 0,2,4; 3,4,5->1,3,5;
对于<n的下标好处理, 直接2j即可
对于>=n的下标呢? 我们有想法将其表示为奇数形式, 2j + 1, 但是这要求他们必须从下标0开始, 这也好办, -n即可, 综合就是2*(j-n) + 1
这里从j=i开始,每次将nums[i]移到nums[j]这个正确的地方,必确打上负号标记
现在的情况是, nums[i]保存着原来nums[j]的值, 变量j保存着该数原来的下标(nums[i]起了一个中介保存的作用);
那我们继续讨论下一个应该存放的位置 j = j < n ? 2 * j : 2 * (j - n) + 1; (这里的j存的就是被换到nums[i]的原下标,我们计算它下一步放到哪)
计算出新的j后,我们将存放在nums[i]的值换到正确的位置,存储下一个等待变更位置的数(nums[j]), 循环往复即可
举个例子:
[2,5,1, | 3,4,7] n = 3
0.1 [-2,5,1,3,4,7] i=0,j=0 (0就应该放到0)
1.1 [-2,1,-5,3,4,7] i=1,j=12=2 (1应该放到2)
1.2 [-2,4,-5,3,-1,7] i=1,j=22=4 (2应该放到4)
1.3 [-2,3,-5,-4,-1,7] i=1,j=2(4-3)+1=3 (4应该放到3,已经是y1了)
1.4 [-2,-3,-5,-4,-1,7] i=1,j=2(3-3)+1=1(3应该放到1)
5.1 [-2,-3,-5,-4,-1,-7] i=5,j=2*(5-3)+1=5(5就应该放到5)
代码
class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
for(int i = 0; i < 2 * n; i ++)
if(nums[i] > 0){
// j 描述当前的 nums[i] 对应的索引,初始为 i
int j = i;
while(nums[i] > 0){
// 计算 j 索引的元素,也就是现在的 nums[i],应该放置的索引
j = j < n ? 2 * j : 2 * (j - n) + 1;
// 把 nums[i] 放置到 j 的位置,
// 同时,把 nums[j] 放到 i 的位置,在下一轮循环继续处理
swap(nums[i], nums[j]);
// 使用负号标记上,现在 j 位置存储的元素已经是正确的元素了
nums[j] = -nums[j];
}
}
for(int& e: nums) e = -e;
return nums;
}
};