问题描述:
- 给定一个数组
nums
,数组中有2n
个元素,按[x1,x2,...,xn,y1,y2,...,yn]
的格式排列。- 将数组按
[x1,y1,x2,y2,...,xn,yn]
格式重新排列,返回重排后的数组。
- 将数组按
- 满足
1 <= nums[i] <= 10^3
。
核心思路:
- 该题虽然是简单题,可以直接用空间复杂度为
O(n)
的方法来解题,但是精彩的地方在于考虑以O(1)
的空间复杂度实现原地排列。 - 有两种方法实现原地解题,均来自一位网友的题解,链接放在本文的最后。
- 方法一:利用位运算。
- 这个方法非常巧妙,因为数组中数值的取值范围为
[1, 1000]
,也就是说所有数值均低于2^10 = 1024
,也就是说所有数值均只有低10
位是有意义的。 - 而一个整型数值有
32
位,可以存储2
个10
位的数值,因此可以用低10
位表示数组中原数值,用中10
位表示重排列后数值。 - 如此就好像是利用数值中剩余的位数组成了新的结果数组。【最后把所有数值右移
10
位即可得到结果】
- 这个方法非常巧妙,因为数组中数值的取值范围为
- 方法二:不断交换直到当前位置正确,满足后将数值置为负数,相当于标记为正确。
- 这个方法是比较常规的类似于原地哈希思想,我一开始也往这个方向想,在当前位置希望通过不断交换数值直到位置满足,但没有想到能用负数来标记结果正确。
- 所谓的原地哈希算法,就是将原数组作为哈希表,建立原数值和对应数值的映射关系,通常是通过不断交换的操作使得数值放置在应该放置的位置上。【具体题目可看 leetcode 41 缺失的第一个正数和 leetcode 287 寻找重复数,这两题是原地哈希的典型题目】
- 方法一其实也是原地哈希算法的体现,但是通常原地哈希算法都伴随着交换,所以方法二更常规。
- 这个方法是比较常规的类似于原地哈希思想,我一开始也往这个方向想,在当前位置希望通过不断交换数值直到位置满足,但没有想到能用负数来标记结果正确。
代码实现:
- 方法一代码实现:【贴自参考内容中链接的题解】
class Solution { public: vector<int> shuffle(vector<int>& nums, int n) { for(int i = 0; i < 2*n; ++i) { int j = i < n ? i*2 : 2*(i-n) + 1; // 要推导出对应关系 nums[j] |= (nums[i] & 1023) << 10; // index i to index j } for(int& num : nums) num >>= 10; return nums; } };
- 方法二代码实现:【贴自参考内容中链接的题解】
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; } };