# 题目
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O (1) 的 原地 算法解决这个问题吗?
示例 1:
输入: nums = [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:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
提示
1 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 105
https://leetcode-cn.com/
# 分析
# 解法 1: 顺序后移法
将最后一个元素赋值给临时变量,然后将其他元素顺序后移一个位置。重复此操作 k 次。
但是需要注意的是:
- 时间复杂度。这种算法在最优时时间复杂度是 O (n). 最差情况下是 O (n^2). 在 LeetCode 上是没法 AC 的。
- k 是会大于数组长度的。
1 | public void rotate(int[] nums, int k) { |
这种解决方法在数据量较大的情况下就会报错 超出时间限制了
。
即使 你在循环修改使用 System.arraycopy(nums, 0, nums, 1, length - 1);
来替换掉内层循环,也是不可以 AC
的。
# 解法 2: 优化解法 1, 空间换时间
按照规律将 nums 数组中的值,赋值到新数组中。然后使用新数组覆盖原数组。
那规律是什么呢?
设:
i
为数组的的下标。 newArr
为新的数组, n
为数组总长度,
那么
1 | newArr[(i+k)%n] = nums[i] |
AC
代码如下:
1 | public static void rotate(int[] nums, int k) { |
AC 之后显示:
执行用时 1 ms
内存消耗: 55.1 MB
这种方式,时间复杂度得到显著的提升,但是带来了额外的空间消耗。而且这种空间消耗是等于原来占用的数据大小。
那是否还有其他方式呢?
# 解法 3: 反转
这种方式是一种比较简单的方式,有点类似于脑筋急转弯的意思。抛开之前的环装思路。将这个数组看成具有方向的一组数据。将这组数据分成三组: [0,k-1]
, k
, [k+1,length]
. 先将 [0,k-1]
反转,再把 [k+1,length]
反转,最后把这个数组反转。这样操作之后,就是最后结果了。
比如,数组: [1,2,3,4,5,6,7]
, k=3
第一步:把整体数组进行反转: [1,2,3,4,5,6,7]
=> [7,6,5,4,3,2,1]
第二步:把 [0,k-1=2]
的元素进行反转: [7,6,5,4,3,2,1]
=> [5,6,7,4,3,2,1]
第三步:把 [k,n-1]
的元素进行反转: [5,6,7,4,3,2,1]
=> [5,6,7,1,2,3,4]
实现代码如下:
1 | public static void rotate(int[] nums, int k) { |
# 思考
关于这道题目的一点小思考:
- 这道题其实还有另外一种解法,就是通过公式去推导出元素应该最终所在的位置,直接进行位置交换。这种思路也是可行的。具体解决可以参照下官网的解题方法。
- 这是一道很简单的题目,最后出奇的解法也是颇感意外的,总有一种豁然开朗的感觉。我一直在想此法的解题人是怎么想到的呢。这里面一定是蕴含一定的逻辑的。这种多次反转的逻辑,或许是一种规律? 我一直没有想通。
- 当你想到这个可以用一个环去解决问题的时候,可能永远就想不出 "利用方向" 去解决这个问题了。反观题目来讲,一直都是说的数组,而 “环” 是我们自己强加的一个思维方式。确实,当我们从环的角度出发,看到的就不是题目本身了,看到的是你的题目。✔️ 反观现实,你理解的现实就真正的是现实吗?你有没有陷入自己的 “环” 中去呢?不妨,重新看看你身边的人和事吧,尝试发现他们最真实的样子.
# 最后
希望与你一起遇见更好的自己