# 题目

给定一个数组,将数组中的元素向右移动 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


# 分析

# 解法 1: 顺序后移法

将最后一个元素赋值给临时变量,然后将其他元素顺序后移一个位置。重复此操作 k 次。

但是需要注意的是:

  • 时间复杂度。这种算法在最优时时间复杂度是 O (n). 最差情况下是 O (n^2). 在 LeetCode 上是没法 AC 的。
  • k 是会大于数组长度的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void rotate(int[] nums, int k) {
if (nums == null || k < 1) {
return;
}
if (nums.length < k) {
// 解决k>nums.length问题
k = k % nums.length;
}
int length = nums.length;
for (int i = 0; i < k; i++) {
int temp = nums[length - 1];
for (int j = length - 1; j > 0; j--) {
nums[j] = nums[j - 1];
}
nums[0] = temp;
}
}

这种解决方法在数据量较大的情况下就会报错 超出时间限制了
即使 你在循环修改使用 System.arraycopy(nums, 0, nums, 1, length - 1); 来替换掉内层循环,也是不可以 AC 的。

# 解法 2: 优化解法 1, 空间换时间

按照规律将 nums 数组中的值,赋值到新数组中。然后使用新数组覆盖原数组。
那规律是什么呢?
设:
i 为数组的的下标。 newArr 为新的数组, n 为数组总长度,
那么

1
newArr[(i+k)%n] = nums[i]

AC 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void rotate(int[] nums, int k) {
if (nums == null || k < 1) {
return;
}

int n = nums.length;
// 新数组
int[] newArr = new int[n];

for (int i = 0; i < k; i++) {
newArr[(i + k) % n] = nums[i];
}

System.arraycopy(newArr, 0, nums, 0, n);
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}

public static void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}

# 思考

关于这道题目的一点小思考:

  • 这道题其实还有另外一种解法,就是通过公式去推导出元素应该最终所在的位置,直接进行位置交换。这种思路也是可行的。具体解决可以参照下官网的解题方法。
  • 这是一道很简单的题目,最后出奇的解法也是颇感意外的,总有一种豁然开朗的感觉。我一直在想此法的解题人是怎么想到的呢。这里面一定是蕴含一定的逻辑的。这种多次反转的逻辑,或许是一种规律? 我一直没有想通。
  • 当你想到这个可以用一个环去解决问题的时候,可能永远就想不出 "利用方向" 去解决这个问题了。反观题目来讲,一直都是说的数组,而 “环” 是我们自己强加的一个思维方式。确实,当我们从环的角度出发,看到的就不是题目本身了,看到的是你的题目。✔️ 反观现实,你理解的现实就真正的是现实吗?你有没有陷入自己的 “环” 中去呢?不妨,重新看看你身边的人和事吧,尝试发现他们最真实的样子.

# 最后

希望与你一起遇见更好的自己

期望与你一起遇见更好的自己