算法与数据结构 · 实战

LC:反转数组

本文写于 2021 年,涉及的组件版本、配置与数据可能已过时,请按经验材料阅读并核对当前版本。原文链接与内容保持不变。

题目

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

:::warning 提示 :::

1 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 105


相关链接

分析

解法1:顺序后移法

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

但是需要注意的是:

  • 时间复杂度. 这种算法在最优时时间复杂度是O(n).最差情况下是O(n^2). 在LeetCode上是没法AC的。
  • k是会大于数组长度的。
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 为数组总长度, 那么

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

AC代码如下:

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]

实现代码如下:

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;
    }
}

思考

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

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

最后

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

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

配套资料 · 免费领取
本文属于「ALGO」资料包范围:算法系列合订 + 刷题清单。LeetCode 与杭电 OJ 的题解索引。
在公众号「方家小白」回复关键词 ALGO 即可领取。全文无需关注即可阅读。
ALGO