手摸手提桶跑路——LeetCode189. 轮转数组

110 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

题目描述

给你一个数组,将数组中的元素向右轮转 k个位置,其中 k是非负数。

示例 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 <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

进阶:

  • 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O(1)原地 算法解决这个问题吗?

解题思路——插入法

[1,2,3,4,5,6,7],k=3 时,可以看做是向右移动 k 位。假设一次移动一位,我们要移动 k 次。我们从最后一个元素来看,向右移动一位相当于把它变成第一个元素,当它变成第一个元素之后,其他元素的相对位置就都向右移动了一位,那么我通过循环,每次都将新的最后一个元素插入到数组头部,是不是就相当于数组整体向右轮转 k 次了?

题解

var rotate = function(nums, k) {
    while(k--) {
        nums.unshift(nums.pop());
    }
    return nums;
};

解题思路——位置计算法

还是拿 [1,2,3,4,5,6,7],k=3 来说,轮转后的数组为 [5,6,7,1,2,3,4]

观察一下这两个数组,发现了什么?

是不是原本第 0 个位置的元素跑第 k 个了?

那第 1 个位置的元素呢?是不是跑第 1 + k 个去了?

那最后一个元素呢?是不是越界了?

对没错,最后一个元素这么算就越界了,为了让它不越界,我们需要对下标进行取模操作,(i+k)%nums.length 。

所以数组的每个元素在轮转 k 次之后的位置就是 (i+k)%nums.length 。

但是如果我们直接对原数组进行操作的话,会覆盖掉原本位置的值,污染数据,所以需要另外开辟一个数组来记录轮转之后的数组,最后将新数组赋值给源数组。

题解

var rotate = function(nums, k) {
    const lens = nums.length;
    const res = [];
    for(let i=0; i<lens; ++i) {
        res[(i+k)%lens] = nums[i]
    }
    for(let i=0; i<lens; ++i) {
        nums[i] = res[i];
    }
    return nums;
};

解题思路——翻转法

官方解法中看到这么一种翻转法

还是拿 [1,2,3,4,5,6,7],k=3 来说:

  1. 将数组进行整体翻转,得到 [7,6,5,4,3,2,1]
  2. 将下标为 [0, k-1] 部分的数组进行翻转,得到 [5,6,7,4,3,2,1]。
  3. 将下标为 [k, length-1] 部分的数组进行翻转,得到 [5,6,7,1,2,3,4]。

最后你会发现结果就是数组向右轮转 k 个位置的结果啦。

题解

const reverse = (nums, start, end) => {
    while(start < end) {
        [nums[start], nums[end]] = [nums[end], nums[start]];
        end--;
        start++;
    }
}
var rotate = function(nums, k) {
    const lens = nums.length;
    nums.reverse();
    k %= lens;
    reverse(nums, 0, k-1);
    reverse(nums, k, lens-1);
    return nums;
};

需要注意的是这里的 k 需要取模,用来处理 nums.length < k 的情况。