先看原题
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的原地算法。
第一种 自己在网上理解的解法:
1、用示例1做例子,先对key进行处理,如果key是负数则表示数组元素右移,所以当key是负数时将这个负数的k转换成所对应的正数。
k = (nums.length + (k % nums.length)) % nums.length; // 保证k为正
2、将整个数组翻转。
for (int i = 0, j = nums.length - 1; i < j; i++, j--) { tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
得到的数组结果为:[7,6,5,4,3,2,1]
3、再将0 - k-1范围内的元素进行翻转
for (int i = 0,j = k-1;i<j;i++,j--){ tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
得到的数组结果为:[5,6,7,4,3,2,1]
4、将剩余部分( k - nums.length )的元素进行翻转
for (int i = k,j= nums.length-1;i<j;i++,j--){ tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
得到的数组结果为:[5,6,7,1,2,3,4]
第二种 leetcode上显示效率最高的解法
先要了解一下System.arraycopy()这个函数,先贴上jdk文档中对于这个函数的解释
arraycopy
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。
被复制的组件的编号等于 length 参数。源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos 到 destPos+length-1 位置。 如果参数 src 和 dest 引用相同的数组对象,则复制的执行过程就好像首先将 srcPos 到 srcPos+length-1 位置的组件复制到一个带有 length 组件的临时数组,
然后再将此临时数组的内容复制到目标数组的 destPos 到 destPos+length-1 位置一样。 If 如果 dest 为 null,则抛出 NullPointerException 异常。 如果 src 为 null, 则抛出 NullPointerException 异常,并且不会修改目标数组。 否则,只要下列任何情况为真,则抛出 ArrayStoreException 异常并且不会修改目标数组: src 参数指的是非数组对象。 dest 参数指的是非数组对象。 src 参数和 dest 参数指的是那些其组件类型为不同基本类型的数组。 src 参数指的是具有基本组件类型的数组且 dest 参数指的是具有引用组件类型的数组。 src 参数指的是具有引用组件类型的数组且 dest 参数指的是具有基本组件类型的数组。 否则,只要下列任何情况为真,则抛出 IndexOutOfBoundsException 异常,并且不会修改目标数组: srcPos 参数为负。 destPos 参数为负。 length 参数为负。 srcPos+length 大于 src.length,即源数组的长度。 destPos+length 大于 dest.length,即目标数组的长度。 否则,如果源数组中 srcPos 到 srcPos+length-1 位置上的实际组件通过分配转换并不能转换成目标数组的组件类型,则抛出 ArrayStoreException 异常。
在这种情况下,将 k 设置为比长度小的最小非负整数,这样就无法将 src[srcPos+k] 转换为目标数组的组件类型;
当抛出异常时,从 srcPos 到 srcPos+k-1 位置上的源数组组件已经被复制到目标数组中的 destPos 到 destPos+k-1 位置,
而目标数组中的其他位置不会被修改。(因为已经详细说明过的那些限制,只能将此段落有效地应用于两个数组都有引用类型的组件类型的情况。) 参数: src - 源数组。 srcPos - 源数组中的起始位置。 dest - 目标数组。 destPos - 目标数据中的起始位置。 length - 要复制的数组元素的数量。 抛出: IndexOutOfBoundsException - 如果复制会导致对数组范围以外的数据的访问。 ArrayStoreException - 如果因为类型不匹配而使得无法将 src 数组中的元素存储到 dest 数组中。 NullPointerException - 如果 src 或 dest 为 null。
简单来说就是将一个数组中的一段元素copy到另一个数组中,这个copy是深copy改变目标数组中的元素不会影响原有的数组。
下面是代码:
int[]num1=new int[nums.length];
k = (nums.length + (k % nums.length)) % nums.length; // 保证k为正
// k=k%nums.length; leet给的示例代码中这样处理k当k是负数是会有问题
System.arraycopy(nums,nums.length-k,num1,0,k);
System.arraycopy(nums,0,num1,k,nums.length-k);
System.arraycopy(num1,0,nums,0,nums.length);
假设给定数组为[1,2,3,4,5,6,7,9],k为-9,处理后的key为7
这段代码思路与上面第一种思路类似,先对k进行正数处理,然后将nums中从length-k开始的元素copy到num1中 0 - k的范围内,得到的num1数组为
[2, 3, 4, 5, 6, 7, 9, 0]
然后将nums中从0开始的元素copy到num1中 k - nums.length-k范围内 得到的num1数组为[2, 3, 4, 5, 6, 7, 9, 1]
最后将num1整个copy回nums数组,实现题目要求的,改变后的结果在原地。(数组还是原来的数组,这里个人有点疑问,这样通过其他数组计算的结果再copy回来到底还算不算原地。不过leetcode中显示这个效率是最高的。)
第三种 leetcode中显示效率最低的解法
if (nums.length == 1) { return; } k = k % nums.length; //k处理成正数应该更合理些,这个解法不考虑key为负数的情况 while (k > 0) { int temp = nums[nums.length - 1]; for (int i = nums.length - 2; i >= 0; i--) { nums[i + 1] = nums[i]; } nums[0] = temp; k--; } for (int i : nums) { System.out.println(i); }
尝试理解了下,应该就是先计算中k的值作为终止条件,然后通过for循环的方式一直将数组前移,知道k的值满足终止条件为止。