题目:
给你一个数组,将数组中的元素向右轮转 k
个位置,其中 k
是非负数
思路:
首先想到了一个递归方法,从头开始执行,每次传入当前需要处理的数组下标,结束递归的目标下标,以及正在等待换位的数值val
递归体内先求出等待换位的数值的新坐标,将待换位数值放入新坐标,同时记录新坐标和对应之前的值作为下次递归的入参,当待处理坐标与结束递归的目标下标相等时结束递归。
问题的关键点在于该递归方法在外层需要遍历多少次。
首先观察数组换位过程,可以知道递归必须在需要处理的数组下标与最开始传入下标一致时结束,代表本次递归中所有涉及的数值都已经到达正确位置。
故外层遍历次数应该与数组长度已经偏移量k相关联,再经过观察发现,一次递归结束,移动的元素个数存在两种情况:
当length可以被k整除时,移动的元素个数为length/k,否则为length(也就是所有元素都一次移动完成);
可知需要遍历的次数为length与k的最大公约数,当然这里有个坑,k如果大于length,需要处理为k%length,减少多余的移动次数和下标越界等问题
解答如下:
class Solution { public void rotate(int[] nums, int k) { k = k > nums.length? k%nums.length : k; int result = 0; int m = nums.length; int n = k; while (n != 0) { result = m % n; m = n; n = result; } for (int i = 0; i < m; i++) { move(nums, i, i, nums[i], k); } } public void move(int[] nums, int index, int end, int val, int k) { int newIndex = index + k; if (newIndex > nums.length - 1) { newIndex -= nums.length; } int tmpVal = nums[newIndex]; nums[newIndex] = val; if (newIndex != end) { move(nums, newIndex, end, tmpVal, k); } } }
总结:
时间复杂度:捞的不谈
空间复杂度:没有开辟额外空间,全程在数组本身进行操作,还不错
进阶扩展:
O(1)
的 原地 算法解决这个问题吗?第二点已经完成,但是应该有更加简单粗暴的做法,由此想出解法二:
原理自己也没想明白,就是观察解法一的流程,发现数组分成两部分,整体倒序后分别再倒序就可以达到效果,在此基础上就简单多了
代码如下:
class Solution { public void rotate(int[] nums, int k) { int length = nums.length; k %= length; reverse(nums, 0, length - 1); reverse(nums, 0, k - 1); reverse(nums, k, length - 1); } public void reverse(int[] nums, int start, int end) { while (start < end) { int tmp = nums[start]; nums[start++] = nums[end]; nums[end--] = tmp; } } }
总结:
不占用额外空间,且效率极高,应该是目前想到最好的解法
应题目要求,想一个解法三,只为锻炼一下思考能力,想到了像链表一样,在k点分成左右两部分,讲左边挂在右边即可
之后只需要考虑如何用数组做到这一点,具体代码如下:
class Solution { public void rotate(int[] nums, int k) { int[] tmps = new int[nums.length]; if (k >= nums.length) { k = k % nums.length; } //遍历左半部分 for (int i = nums.length - k; i < nums.length; i++) { tmps[i+k-nums.length] = nums[i]; } //遍历右半部分 for (int i = 0; i < nums.length - k; i++) { tmps[i+k] = nums[i]; } for (int i = 0; i < nums.length; i++) { nums[i] = tmps[i]; } } }
总结:
其实还是有点绕,写的时候一度把自己绕晕了,左右部分究竟是在遍历谁,处理谁,容易搞乱。