力扣-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的下标呢? 我们有想法将其表示为奇数形式, 2
j + 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=2
2=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;
    }
};
posted @ 2024-04-28 16:58  DawnTraveler  阅读(3)  评论(0编辑  收藏  举报