[附C++,JS,Python题解] Leetcode 面试150题(10)——轮转数组

一、题目

189. 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 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. 搞清楚题目给的数组变化逻辑------轮换k个位置其实就是把[l-k,l-1]的这几个元素放到[0,k-1]的位置上去;

  2. 明显可以看到如果是线性表,在轮换过程中会引起很多移动,时间复杂度高,在这里选择哪种方式来优化会引出多种解法------

① 解法一思路:

题目提到原地修改的要求,想到双指针,又想到双指针的核心思想------两头开工。在这个问题中我们就可以分成要往前挪的一头和往后挪的一头,这种情况下怎么样才能减少移动?

我们可以使用两个指针,一个指针从数组的开头开始,另一个指针从数组的末尾开始。通过交换这两个指针指向的元素,可以在不需要额外空间的情况下完成轮换。具体步骤如下:

  • 首先,将数组分成两部分:前 l-k 个元素和后 k 个元素。
  • 使用两个指针,一个指向前 l-k 个元素的末尾,另一个指向后 k 个元素的开头。
  • 交换这两个指针指向的元素,直到两个指针相遇。
  • 最后,将前 l-k 个元素和后 k 个元素分别进行反转,即可得到轮换后的数组。

这种方式是原地修改,很符合题目要求。

② 解法二思路:

移动的规律使得数组移动前后对应位置的元素也有规律,为了避免在原数组上进行复杂的移动操作,可以创建一个新的数组 news 来存储轮转后的结果。新数组的长度与原数组相同,即 l

  • 对于原数组中的每个元素 nums[i],其在新数组中的位置可以通过 (i + k) % l 计算得出。
  • 这个公式的意义是:将原数组的索引 i 向右移动 k 个位置,如果超出数组长度,则通过取模运算 % l 回到数组的开头。
  • 例如,对于 nums[0],其新位置是 (0 + k) % l,对于 nums[1],其新位置是 (1 + k) % l,依此类推。

但是这种方式并非原地修改,而是定义新数组来处理。

③ 解法三思路:

几乎每种语言都有自带的切片和拼接函数,这个问题显然也可以用到。方法也很直接,把要挪到后面的l-k个元素和要挪到前面的k个元素都切下来换个顺序拼一起即可。

需要注意的是切片拼接后也是返回一个新数组而不是原地修改nums。

补充一点不同语言中切片和拼接函数的用法:

|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | 切片函数 | 拼接函数 |
| JavaScript | array.slice(start,end) | array.concat() |
| JavaScript | * 第一个参数 start(可选):开始提取的索引位置,默认为 0。若为负数,则从数组末尾开始计算。 * 第二个参数 end(可选):提取结束的索引位置(不包含该位置的元素)。若省略,则提取到数组末尾;若为负数,则从数组末尾开始计算。 | * 可以传入任意数量的数组或值,这些数组或值会被拼接在一起。 * 返回一个新数组,包含原数组和拼接的数组或值。 |
| JavaScript | * 适用数据类型 :数组和类数组对象(如 arguments 对象、NodeList 等)。 | * 适用数据类型:数组。 |
| C++ | 使用 vectorstring 结合迭代器来实现: vector--sliced | vector--insert string--append或者+ |
| C++ | cpp vector<int> vec = {1, 2, 3, 4, 5}; vector<int> sliced(vec.begin() + 1, vec.begin() + 3); | cpp vector<int> vec1 = {1, 2}; vector<int> vec2 = {3, 4}; vec1.insert(vec1.end(), vec2.begin(), vec2.end()); cpp string str1 = "Hello"; string str2 = " World"; string combined = str1 + str2; |
| Python | [start:stop:step] 比如: > my_list = [1, 2, 3, 4, 5] > sliced = my_list[1:3] * start(可选):开始提取的索引位置,默认为 0。若为负数,则从序列末尾开始计算。 * stop(可选):提取结束的索引位置(不包含该位置的元素)。若省略,则提取到序列末尾;若为负数,则从序列末尾开始计算。 * step(可选):切片的步长,默认为 1。若为负数,则表示从后往前切片。 | + 运算符 比如: > list1 = [1, 2] > list2 = [3, 4] > combined = list1 + > list2 参数:需要拼接的两个或多个序列。 |
| Python | * 适用数据类型:字符串、列表、元组等序列类型。 | * 适用数据类型:字符串、列表、元组等序列类型。 |

三、代码

解法一(原地修改+反转)

① JS:

javascript 复制代码
function ChangeOver(nums, k) {
    let l = nums.length;
    k = k % l;
    // 使用反转的方法进行原地修改
    reverse(nums, 0, l - 1);
    reverse(nums, 0, k - 1);
    reverse(nums, k, l - 1);
}

function reverse(nums, start, end) {
    while (start < end) {
        let temp = nums[start];
        nums[start] = nums[end];
        nums[end] = temp;
        start++;
        end--;
    }
}

② Python:

python 复制代码
def change_over(nums, k):
    l = len(nums)
    k = k % l
    reverse(nums, 0, l - 1)
    reverse(nums, 0, k - 1)
    reverse(nums, k, l - 1)

def reverse(nums, start, end):
    while start < end:
        nums[start], nums[end] = nums[end], nums[start]
        start += 1
        end -= 1

③ C++:

cpp 复制代码
void reverse(vector<int>& nums, int start, int end) {
    while (start < end) {
        swap(nums[start], nums[end]);
        start++;
        end--;
    }
}

void ChangeOver(vector<int>& nums, int k) {
    int l = nums.size();
    k = k % l;
    reverse(nums, 0, l - 1);
    reverse(nums, 0, k - 1);
    reverse(nums, k, l - 1);
}

解法二(找规律+新数组)

① JS:

javascript 复制代码
function changeOver(nums,k){
    let l = nums.length;
    let news = new Array(l);
    k = k % l; // 处理 k 大于数组长度的情况
    for(let i = 0; i < l; i++) {
        news[(i + k) % l] = nums[i];
    }
    return news;
}
// let nums1 = [1, 2, 3, 4, 5, 6, 7];
// let k1 = 3;
// console.log(changeOver(nums1, k1));
// 上述部分用于测试输出

② Python:

python 复制代码
def change_over(nums, k):
    l = len(nums)
    k = k % l
    news = [0] * l  # 创建一个长度为 l 的新数组
    for i in range(l):
        news[(i + k) % l] = nums[i]
    return news

③ C++:

cpp 复制代码
vector<int> changeOver(vector<int>& nums, int k) {
    int l = nums.size();
    k = k % l;
    vector<int> news(l);
    for (int i = 0; i < l; i++) {
        news[(i + k) % l] = nums[i];
    }
    return news;
}

解法三(切片+拼接):

① JS:

javascript 复制代码
function changeOver(nums,k){
    let l = nums.length;
    k = k % l; // 处理 k 大于数组长度的情况
    return nums.slice(-k).concat(nums.slice(0, -k));
}

② Python:

python 复制代码
def change_over(nums, k):
    l = len(nums)
    k = k % l
    return nums[-k:] + nums[:-k]

③ C++:

cpp 复制代码
vector<int> changeOver(vector<int>& nums, int k) {
    int l = nums.size();
    k = k % l;  // 处理 k 大于数组长度的情况
    vector<int> result;

    // 将后 k 个元素添加到结果中
    for (int i = l - k; i < l; i++) {
        result.push_back(nums[i]);
    }

    // 将前 l-k 个元素添加到结果中
    for (int i = 0; i < l - k; i++) {
        result.push_back(nums[i]);
    }

    return result;
}

四、总结

  1. 处理k<l的这个约束条件时,用k=k%l代替while(k<l)来简化代码。取模运算是一个常数时间操作,能够直接计算出 k 在数组长度范围内的等效值,非常适合处理轮转次数大于数组长度的情况。

  2. 原地修改这一要求使得解法二、三无法通过力扣题解,解法一之外还有哪些更优的解法尚需进一步思考和尝试。

相关推荐
愚润求学14 分钟前
【C++】vector常用方法总结
开发语言·c++·vector
天天进步201524 分钟前
Python项目-基于Flask的个人博客系统设计与实现(1)
开发语言·python·flask
安然无虞26 分钟前
31天Python入门——第20天:魔法方法详解
开发语言·后端·爬虫·python
靠近彗星37 分钟前
基于 Vue + Django + MySQL 实现个人博客/CMS系统
前端·vue.js·python·mysql·django
励志成为大佬的小杨1 小时前
pytorch模型的进阶训练和性能优化
人工智能·pytorch·python
赤秀1 小时前
C++模板初阶
开发语言·c++
半桔1 小时前
红黑树剖析
c语言·开发语言·数据结构·c++·后端·算法
江烽渔火1 小时前
C++ 多态
开发语言·c++
今天_也很困2 小时前
牛客2025年愚人节比赛
c++·算法
嘤国大力士2 小时前
C++11&QT复习 (十)
java·c++·qt