[Leetcode][Rotate Array]一种对连续内存空间进行的循环移位的巧妙解法(O(1)的空间、O(n)的时间)
2013-03-22 16:57
411 查看
本文最初发表:http://03071344.lofter.com/post/10871e_2c4556
LeetCode对应题目:Rotate Array
本文讨论如何对一段连续的内存空间(如int型数组、char型数组)的内容进行循环移动。例如我们有一个数组,定义如下:int num[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};现要对num数组的内容循环移动4位。
temp -> 6, 7, 8, 9
然后从后往前将num中的元素依次向后移动4位,结果为:
num -> 1, 2, 3, 4, 1, 2, 3, 4, 5
最后,再将temp中的元素放置在num指向的内存空间的前方。于是
num -> 6, 7, 8, 9, 1, 2, 3, 4, 5
这就得到了我们想要的结果。
首先,申请9-7=2个单位的内存空间,起始地址为temp。将num指向的前面两个单位的内容放置在temp中。即
temp -> 1, 2
其次,循环移动num,但不是往后移动,而是往前移动。移动后的结果为:
num -> 3, 4, 5, 6, 7, 8, 9, 8, 9
最后,将temp中的值赋值给num末端,结果为:
num -> 3, 4, 5, 6, 7, 8, 9, 1, 2
完成循环移动。
例如要将num循环移动1位。则利用一个单位的temp作为中间变量,然后将从num[1]开始一直到num[8],依次和num[0]交换内容。
若要将num循环移动m位,那么可以循环调用上述移动方案m次。于是总的移动次数为m*9。(为方便起见,后面我们都用m表示要循环移动的位数、n为要进行循环移位的数组的长度,在这里n == 9)
其中d = gcd(m, n),即d为m、n的最大公约数。由于在使用m之后都要进行模n运算,我们可以对m进行预处理,也削减一些不必要的计算。令m = m % n即可。
上述算法的正确可以利用数论中完全剩余系的知识进行证明。我们可以将要移动的数组num想象成一个长为n的环。从环上某一点开始,然后每次前进m步。经过n/d步之后,将回到原点。这就完成了环上一些零碎点的循环移动。外层循环 for (i = 0; i < d; ++i) 保证环上所有的元素均向后移动m步。
上述算法即保证了每个元素都向后循环移动m步(且只移动一次),又将额外的内存消耗从O(m)降至了O(1)。当然,代价是引入了额外数值计算。如求最大公约数。
附:最后一种解法的完整代码如下。
翻转[1,2,3,4]部分,得到[4,3,2,1,5,6,7]
翻转[5,6,7]部分,得到[4,3,2,1,7,6,5]
翻转整个数组,得到[5,6,7,1,2,3,4],也就是最终答案。
可以看到这种方法,只要写一个翻转数组的函数,然后调用三次即可。
详见:http://segmentfault.com/a/1190000002601872
public class Solution {
public void rotate(int[] nums, int k) {
if (nums.length == 0 || nums.length == 1 || k % nums.length == 0)
return;
k %= nums.length;
int length = nums.length;
reverse(nums, 0, length - k - 1);
reverse(nums, length - k, length - 1);
reverse(nums, 0, length - 1);
}
private void reverse(int[] nums, int begin, int end) {
for (int i = 0; i < (end - begin + 1) / 2; i++) {
int temp = nums[begin + i];
nums[begin + i] = nums[end - i];
nums[end - i] = temp;
}
}
}
LeetCode对应题目:Rotate Array
本文讨论如何对一段连续的内存空间(如int型数组、char型数组)的内容进行循环移动。例如我们有一个数组,定义如下:int num[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};现要对num数组的内容循环移动4位。
解法一
我们可以申请4个连续的int变量空间temp[4],然后将num末4位元素放置在temp中。即temp -> 6, 7, 8, 9
然后从后往前将num中的元素依次向后移动4位,结果为:
num -> 1, 2, 3, 4, 1, 2, 3, 4, 5
最后,再将temp中的元素放置在num指向的内存空间的前方。于是
num -> 6, 7, 8, 9, 1, 2, 3, 4, 5
这就得到了我们想要的结果。
解法二
我们可以对上述解法进行优化。特别地,当要移动的位数超过原数组的长度的一半的时候,我们可以循环的特点减少所需的额外的内存空间。假如如要将上述num向后循环移动7个单位。则步骤如下:首先,申请9-7=2个单位的内存空间,起始地址为temp。将num指向的前面两个单位的内容放置在temp中。即
temp -> 1, 2
其次,循环移动num,但不是往后移动,而是往前移动。移动后的结果为:
num -> 3, 4, 5, 6, 7, 8, 9, 8, 9
最后,将temp中的值赋值给num末端,结果为:
num -> 3, 4, 5, 6, 7, 8, 9, 1, 2
完成循环移动。
解法三
事实上,我们还有另一种方案。只需要额外的1个单位空间即可(事实上,如果将任意类型的连续空间都看成连续的内存字节,则只需要一个字节即可,下同)。例如要将num循环移动1位。则利用一个单位的temp作为中间变量,然后将从num[1]开始一直到num[8],依次和num[0]交换内容。
若要将num循环移动m位,那么可以循环调用上述移动方案m次。于是总的移动次数为m*9。(为方便起见,后面我们都用m表示要循环移动的位数、n为要进行循环移位的数组的长度,在这里n == 9)
解法四
上述方案不需要太多额外的空间开销,但是进行移动操作(即交换操作)的步骤太多。应设法削减。一个直观的想法就是对每一个元素,都直接将它向后循环移动指定的m位数。下面是根据这一思路编写的代码的核心部分:for (i = 0; i < d; ++i) { k = 1; while (k <= n / d) { temp = num[i]; num[i] = num[(i + k * m) % n]; num[(i + k * m) % n] = temp; ++k; } }
其中d = gcd(m, n),即d为m、n的最大公约数。由于在使用m之后都要进行模n运算,我们可以对m进行预处理,也削减一些不必要的计算。令m = m % n即可。
上述算法的正确可以利用数论中完全剩余系的知识进行证明。我们可以将要移动的数组num想象成一个长为n的环。从环上某一点开始,然后每次前进m步。经过n/d步之后,将回到原点。这就完成了环上一些零碎点的循环移动。外层循环 for (i = 0; i < d; ++i) 保证环上所有的元素均向后移动m步。
上述算法即保证了每个元素都向后循环移动m步(且只移动一次),又将额外的内存消耗从O(m)降至了O(1)。当然,代价是引入了额外数值计算。如求最大公约数。
附:最后一种解法的完整代码如下。
#include <stdio.h> #define n 15 int gcd(int a, int b) { if (a * b == 0) { return a + b; } return gcd(b, a % b); } int main() { char str ; char temp; int m, d, i, k; for (i = 0; i < n; ++i) { str[i] = 'a' + i; } scanf("%d", &m); m = m % n; d = gcd(n, m); for (i = 0; i < d; ++i) { k = 1; while (k <= n / d) { temp = str[i]; str[i] = str[(i + k * m) % n]; str[(i + k * m) % n] = temp; ++k; } } for (i = 0; i < n; ++i) { printf("%c", str[i]); } printf("\n"); return 0; }
解法五
在编程珠玑中提到的翻转方法,比如我们的输入是[1,2,3,4,5,6,7]和k = 3,那么翻转需要如下三部:翻转[1,2,3,4]部分,得到[4,3,2,1,5,6,7]
翻转[5,6,7]部分,得到[4,3,2,1,7,6,5]
翻转整个数组,得到[5,6,7,1,2,3,4],也就是最终答案。
可以看到这种方法,只要写一个翻转数组的函数,然后调用三次即可。
详见:http://segmentfault.com/a/1190000002601872
public class Solution {
public void rotate(int[] nums, int k) {
if (nums.length == 0 || nums.length == 1 || k % nums.length == 0)
return;
k %= nums.length;
int length = nums.length;
reverse(nums, 0, length - k - 1);
reverse(nums, length - k, length - 1);
reverse(nums, 0, length - 1);
}
private void reverse(int[] nums, int begin, int end) {
for (int i = 0; i < (end - begin + 1) / 2; i++) {
int temp = nums[begin + i];
nums[begin + i] = nums[end - i];
nums[end - i] = temp;
}
}
}
相关文章推荐
- Leetcode 238 Product of Array Except Self 时间O(n)和空间O(1)解法
- LeetCode189——Rotate Array两种解法(一种易读,一种高效)
- Leetcode 238 Product of Array Except Self 时间O(n)和空间O(1)解法
- Leetcode 234 Palindrome Linked List 复杂度为时间O(n) 和空间(1)解法
- 2015.03.29 LeetCode Rotate array LeetCode java 解法
- LeetCode--Rotate Array(2重循环,n-1次swap)
- Leetcode 234 Palindrome Linked List 复杂度为时间O(n) 和空间(1)解法
- LeetCode 33 Search in Rotated Sorted Array(循环有序数组中进行查找操作)
- leetcode之189. Rotate Array(C++解法)
- leetcode [Rotate Array]//待整理多种解法
- 【LeetCode】189 Rotate Array 小侃小解
- 一层for循环完成排序--空间换取时间
- LeetCode 73. Set Matrix Zeroes (O(nm)时间 O(1)空间实现)
- 【LeetCode】215. Kth Largest Element in an Array,基于Java和C++的解法
- 独木舟上的旅行 时间限制:3000 ms | 内存限制:65535 KB 难度:2 描述 进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别。一条独木舟最多只能乘坐两个人,且乘客的总
- Oracle使用rman进行表空间基于时间点的恢复
- LeetCode:Rotate Array
- LeetCode 189. Rotate Array
- [字符串] 一个巧妙的字符串循环移位法
- [LeetCode] Rotate Array