一、前置必备知识
在做这道题之前,我们必须先掌握几个最基础的知识点,哪怕你完全没学过算法、没写过代码,看完这部分也能跟上节奏,所有知识点都配"大白话+例子",不搞专业术语堆砌。
1. 什么是数组(列表)?
简单说,数组就是"把多个数字(或其他数据)按顺序排好队,放在一个容器里",这个容器有固定的长度(元素个数),每个元素都有一个"位置编号",这个编号就是「索引」(也叫下标)。
重点注意:所有编程语言中,数组的索引都是从 0 开始的(不是从1开始!这是最容易踩的坑)。
例子(对应题目示例1):nums = [1,2,3,4,5,6,7]
-
数组长度(元素个数):7个
-
元素1的索引是 0,元素2的索引是 1,......,元素7的索引是 6
-
访问元素:想拿到元素7,就用 nums[6](容器名+[索引]);想拿到元素3,就用 nums[2]
补充:Python中叫"列表"(list),C++中叫"向量"(vector),本质完全一样,都是数组,后续统一叫"数组"。
2. 什么是"原地修改"?
题目要求"modify nums in-place instead"(原地修改),大白话就是:不能新建一个和原数组一样大的数组来存结果,必须直接修改题目给你的那个数组。
举个反例:如果原数组是 [1,2,3],你新建一个数组 [3,1,2] 来存轮转后的结果,这就不是原地修改;必须直接把原数组 [1,2,3] 改成 [3,1,2],才符合要求。
为什么要原地修改?因为如果数组很大(比如题目中说的10^5个元素),新建数组会占用大量内存,效率很低,这也是面试中考察的重点。
3. 取模运算(%)的作用(重中之重)
取模运算就是"求余数",比如 10 ÷ 7 = 1 余 3,那么 10 % 7 = 3;5 ÷ 5 = 1 余 0,那么 5 % 5 = 0。
这道题中,取模的作用是「简化k的值」,为什么需要简化?
因为数组轮转是"循环的":比如数组长度n=7,向右轮转7步,相当于没轮转(元素回到原来的位置);轮转8步,相当于轮转1步;轮转10步,相当于轮转3步。
结论:k = k % n(n是数组长度),这样可以把k简化到 0 ≤ k < n 的范围,避免做无用功。
例子:n=7,k=10 → 10%7=3 → 轮转10步 ≡ 轮转3步;n=4,k=6 → 6%4=2 → 轮转6步 ≡ 轮转2步。
4. 数组反转(三次反转法会用到)
反转就是"把数组的元素顺序颠倒过来",比如 [1,2,3,4] 反转后是 [4,3,2,1];[5,6,7] 反转后是 [7,6,5]。
反转的核心操作:用两个指针(一个在最左边,一个在最右边),交换两个指针指向的元素,然后左指针向右移、右指针向左移,直到两个指针相遇(或交叉)。
手动演示:反转 [1,2,3,4]
-
左指针=0(指向1),右指针=3(指向4)→ 交换 → [4,2,3,1]
-
左指针=1(指向2),右指针=2(指向3)→ 交换 → [4,3,2,1]
-
左指针=2,右指针=1 → 交叉,结束。
5. 基础语法补充(必看)
(1)Python基础:
-
列表定义:nums = [1,2,3](直接用方括号,里面放元素)
-
列表长度:len(nums)(获取列表中元素的个数)
-
列表取值/赋值:nums[0] = 5(把索引0的元素改成5);last = nums[-1](获取最后一个元素,nums[-1] 等价于 nums[len(nums)-1])
-
for循环:for _ in range(k) → 循环k次,下划线"_"表示"这个循环变量用不到,只是单纯循环次数"
-
函数定义:def 函数名(参数): → 定义一个函数,参数是传入的数值(比如nums和k)
(2)C++基础:
-
vector定义:vector<int> nums = {1,2,3}(vector<int> 表示"存整数的数组")
-
数组长度:nums.size()(获取vector中元素的个数,和Python的len(nums)一样)
-
数组取值/赋值:nums[0] = 5(和Python一样);nums.back()(获取最后一个元素,等价于Python的nums[-1])
-
swap函数:swap(a, b) → 交换a和b的值(比如swap(nums[0], nums[1]),会把索引0和1的元素交换)
-
函数参数:vector<int>& nums → 这里的"&"表示"引用",作用是"直接操作原数组",实现原地修改(如果没有"&",会复制一个新数组,修改后原数组不变)
-
main函数:C++程序的入口,所有代码都要在main函数中调用才能运行(Python不需要,但我们会用if name == "main" 模拟入口)
二、题目解析
题目:给定一个整数数组nums,将数组中的元素「向右轮转k个位置」,k是非负数,并且要求「原地修改」原数组。
大白话翻译:把数组的"最后k个元素"搬到数组的"最前面",剩下的"前n-k个元素"依次往后挪,不新建数组,直接改原数组。
示例1拆解(能看懂的步骤):
输入:nums = [1,2,3,4,5,6,7],k=3
n = 7(数组长度),k=3(无需简化,因为3<7)
向右轮转1步:把最后一个元素7搬到最前面,其余元素后移 → [7,1,2,3,4,5,6]
向右轮转2步:再把最后一个元素6搬到最前面,其余元素后移 → [6,7,1,2,3,4,5]
向右轮转3步:再把最后一个元素5搬到最前面,其余元素后移 → [5,6,7,1,2,3,4](最终结果)
示例2拆解:
输入:nums = [-1,-100,3,99],k=2
n=4,k=2(无需简化)
轮转1步:[99,-1,-100,3]
轮转2步:[3,99,-1,-100](最终结果)
关键提醒:如果k=0,说明不需要轮转,数组保持原样即可(比如nums=[1,2,3],k=0,输出还是[1,2,3])。
三、三种解题方法(由易到难,暴力→最优,双语言+注释+运行流程)
我们将讲解3种解法,从"最容易理解但效率低"的暴力法,到"面试必考、效率最高"的三次反转法,每种解法都包含:核心思路、复杂度分析、双语言代码(每句注释)、运行过程(示例一步步走)、调用流程,确保能跟着走一遍就懂。
方法1:暴力逐次轮转法(最简单,入门首选)
1. 核心思路
既然向右轮转k步,那就先实现"向右轮转1步",然后重复执行k次,就能得到最终结果。
轮转1步的步骤(关键):
① 先保存数组的最后一个元素(因为后面元素后移会覆盖它);
② 从数组的最后一个元素(索引n-1)开始,依次把前一个元素的值赋给当前元素(比如索引6 = 索引5的值,索引5 = 索引4的值......直到索引1 = 索引0的值);
③ 把第一步保存的"最后一个元素"放到数组的第一个位置(索引0)。
2. 复杂度分析(能懂的解释)
- 时间复杂度:O(n×k)(n是数组长度)
解释:每次轮转1步,需要遍历n-1个元素(后移);总共轮转k次,所以总操作次数是k×(n-1),近似为n×k。如果n和k都很大(比如n=10^5,k=10^5),操作次数就是10^10次,效率极低,实际开发/面试中绝对不用,但适合理解题意。
- 空间复杂度:O(1)(仅用1个临时变量保存最后一个元素,不新建数组,符合原地修改要求)
3. Python代码(每句注释,能看懂)
python
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
注释:题目要求原地修改,所以这个函数不返回任何值,直接修改传入的nums列表
"""
# 1. 获取数组长度n(注意:len(nums)就是数组中元素的个数)
n = len(nums)
# 2. 简化k的值:避免k大于n导致的无用轮转(核心操作,不能少)
k = k % n
# 3. 轮转k次,每次轮转1步(for循环执行k次)
# 这里的下划线"_"表示循环变量用不到,只是单纯执行k次
for _ in range(k):
# 3.1 保存数组最后一个元素(因为后面元素后移会覆盖它,必须先保存)
last = nums[-1] # nums[-1]就是数组最后一个元素,等价于nums[n-1]
# 3.2 所有元素向后移动1位(从最后一个元素开始,向前遍历)
# 循环条件:i从n-1(最后一个元素索引)开始,到1结束(不包括0),每次减1
for i in range(n-1, 0, -1):
# 把前一个元素(i-1)的值,赋给当前元素(i),实现后移
nums[i] = nums[i-1]
# 3.3 把保存的最后一个元素,放到数组的第一个位置(索引0)
nums[0] = last
# 主函数(测试用例,注意:这部分是用来运行代码、查看结果的,必须有!)
if __name__ == "__main__":
# 测试示例1(题目给的第一个案例)
nums1 = [1,2,3,4,5,6,7]
k1 = 3
# 创建Solution类的对象(:可以理解为"调用这个函数的工具")
solution = Solution()
# 调用rotate函数,传入nums1和k1(原地修改nums1)
solution.rotate(nums1, k1)
# 输出修改后的结果,验证是否正确
print("示例1输出:", nums1) # 预期输出:[5,6,7,1,2,3,4]
# 测试示例2(题目给的第二个案例)
nums2 = [-1,-100,3,99]
k2 = 2
solution.rotate(nums2, k2)
print("示例2输出:", nums2) # 预期输出:[3,99,-1,-100]
# 自定义测试用例1(k大于数组长度,测试取模的作用)
nums3 = [1,2,3]
k3 = 4 # n=3,k3%3=1,等价于轮转1步
solution.rotate(nums3, k3)
print("自定义测试1输出:", nums3) # 预期输出:[3,1,2]
# 自定义测试用例2(k=0,无需轮转)
nums4 = [10,20,30,40]
k4 = 0
solution.rotate(nums4, k4)
print("自定义测试2输出:", nums4) # 预期输出:[10,20,30,40]
4. Python代码运行过程(示例1一步步走,必看)
输入:nums1 = [1,2,3,4,5,6,7],k1=3 → n=7,k=3
第一次循环(轮转第1步):
① last = nums[-1] = 7;
② 循环i从6到1(i=6,5,4,3,2,1):
i=6:nums[6] = nums[5] → nums[6] = 6 → 数组变成 [1,2,3,4,5,6,6]
i=5:nums[5] = nums[4] → nums[5] = 5 → 数组变成 [1,2,3,4,5,5,6]
i=4:nums[4] = nums[3] → 4 → 数组 [1,2,3,4,4,5,6]
i=3:nums[3] = nums[2] → 3 → 数组 [1,2,3,3,4,5,6]
i=2:nums[2] = nums[1] → 2 → 数组 [1,2,2,3,4,5,6]
i=1:nums[1] = nums[0] → 1 → 数组 [1,1,2,3,4,5,6]
③ nums[0] = last =7 → 数组变成 [7,1,2,3,4,5,6](轮转1步完成)
第二次循环(轮转第2步):
① last = nums[-1] =6;
② 元素后移,最终数组变成 [7,7,1,2,3,4,5];
③ nums[0] =6 → 数组变成 [6,7,1,2,3,4,5](轮转2步完成)
第三次循环(轮转第3步):
① last = nums[-1] =5;
② 元素后移,最终数组变成 [6,6,7,1,2,3,4];
③ nums[0] =5 → 数组变成 [5,6,7,1,2,3,4](轮转3步完成,和预期一致)
5. C++代码(每句注释,能看懂)
cpp
#include <iostream> // 用于输入输出(比如cout打印结果)
#include <vector> // 用于使用vector(C++的数组)
using namespace std; // 简化代码,不用每次写std::cout、std::vector
class Solution {
public:
// 函数定义:void表示无返回值,vector<int>& nums表示"引用传递",直接修改原数组
// k是轮转的步数,非负数
void rotate(vector<int>& nums, int k) {
// 1. 获取数组长度n(nums.size()就是数组元素个数,和Python的len(nums)一样)
int n = nums.size();
// 2. 简化k的值,避免无用轮转(核心操作,不能少)
k = k % n;
// 3. 轮转k次,每次轮转1步(for循环执行k次)
for (int i = 0; i < k; i++) { // i从0到k-1,共k次循环
// 3.1 保存数组最后一个元素(nums.back()就是最后一个元素,等价于nums[n-1])
int last = nums.back();
// 3.2 所有元素向后移动1位(从最后一个元素开始,向前遍历)
// 循环条件:j从n-1(最后一个索引)到1(不包括0),每次减1
for (int j = n - 1; j > 0; j--) {
// 把前一个元素(j-1)的值,赋给当前元素(j),实现后移
nums[j] = nums[j - 1];
}
// 3.3 把保存的最后一个元素,放到数组第一个位置(索引0)
nums[0] = last;
}
}
};
// 主函数(C++程序的入口,所有代码必须在这里调用才能运行)
int main() {
// 测试示例1(题目给的第一个案例)
vector<int> nums1 = {1,2,3,4,5,6,7}; // 定义C++数组(vector)
int k1 = 3;
Solution solution; // 创建Solution类的对象(用于调用rotate函数)
solution.rotate(nums1, k1); // 调用rotate函数,原地修改nums1
cout << "示例1输出:";
for (int num : nums1) { // 遍历数组,打印每个元素
cout << num << " ";
}
cout << endl; // 换行,让输出更整洁(预期输出:5 6 7 1 2 3 4)
// 测试示例2(题目给的第二个案例)
vector<int> nums2 = {-1,-100,3,99};
int k2 = 2;
solution.rotate(nums2, k2);
cout << "示例2输出:";
for (int num : nums2) {
cout << num << " ";
}
cout << endl; // 预期输出:3 99 -1 -100
// 自定义测试用例1(k大于数组长度,测试取模)
vector<int> nums3 = {1,2,3};
int k3 = 4; // n=3,k3%3=1,等价于轮转1步
solution.rotate(nums3, k3);
cout << "自定义测试1输出:";
for (int num : nums3) {
cout << num << " ";
}
cout << endl; // 预期输出:3 1 2
// 自定义测试用例2(k=0,无需轮转)
vector<int> nums4 = {10,20,30,40};
int k4 = 0;
solution.rotate(nums4, k4);
cout << "自定义测试2输出:";
for (int num : nums4) {
cout << num << " ";
}
cout << endl; // 预期输出:10 20 30 40
return 0; // 主函数结束,返回0表示程序正常运行
}
6. C++代码调用流程(必看)
① 先引入必要的头文件(iostream用于打印,vector用于数组);
② 定义Solution类,里面有rotate函数(核心逻辑,和Python的rotate函数功能一样);
③ 定义main函数(程序入口),在main函数中:
a. 定义测试用例的数组(vector)和k值;
b. 创建Solution对象(solution);
c. 调用solution.rotate(数组, k),原地修改数组;
d. 用for循环遍历数组,打印修改后的结果,验证是否正确。
方法2:额外数组法(空间换时间,高效易懂,面试常用)
1. 核心思路
暴力法效率低,是因为要重复轮转k次,我们可以换个思路:直接计算每个元素轮转后的最终位置,把元素放到新数组的对应位置,最后再把新数组的内容复制回原数组(满足原地修改要求)。
关键公式(核心中的核心):元素的新位置 = (原索引 + k) % n
解释:
-
原索引:元素原来的位置(比如示例1中,元素1的原索引是0);
-
新位置:轮转后元素应该在的位置(比如元素1,原索引0,k=3,n=7 → 新位置=(0+3)%7=3 → 轮转后元素1在索引3的位置);
-
为什么用取模?因为元素轮转后会循环,比如原索引6(元素7),k=3,n=7 → 新位置=(6+3)%7=9%7=2 → 元素7在索引2的位置(和示例1结果一致:[5,6,7,1,2,3,4]中,7在索引2)。
2. 复杂度分析
-
时间复杂度:O(n)(仅遍历数组1次,把元素放到新数组,再遍历1次复制回原数组,总操作次数是2n,近似为n,效率比暴力法高很多);
-
空间复杂度:O(n)(需要新建一个和原数组等长的新数组,用来存元素的最终位置,空间换时间)。
3. Python代码(每句注释,能看懂)
python
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
注释:原地修改,不返回任何值,直接修改传入的nums
"""
# 1. 获取数组长度n
n = len(nums)
# 2. 简化k的值,避免无用轮转(核心操作,不能少)
k = k % n
# 3. 新建一个和原数组等长的空数组res,用来存轮转后的元素
# [0] * n 表示创建一个长度为n、每个元素都是0的列表
res = [0] * n
# 4. 遍历原数组,把每个元素放到新数组的对应位置
for i in range(n):
# 核心公式:新位置 = (原索引i + k) % n
new_index = (i + k) % n
# 把原数组索引i的元素,放到新数组new_index的位置
res[new_index] = nums[i]
# 5. 把新数组res的内容,复制回原数组nums(实现原地修改)
# 注意:必须用nums[:] = res,不能用nums = res(后者是重新赋值,不会修改原数组)
nums[:] = res
# 主函数(测试用例,和方法1一致,用于验证结果)
if __name__ == "__main__":
# 测试示例1
nums1 = [1,2,3,4,5,6,7]
k1 = 3
solution = Solution()
solution.rotate(nums1, k1)
print("示例1输出:", nums1) # 预期:[5,6,7,1,2,3,4]
# 测试示例2
nums2 = [-1,-100,3,99]
k2 = 2
solution.rotate(nums2, k2)
print("示例2输出:", nums2) # 预期:[3,99,-1,-100]
# 自定义测试1(k大于n)
nums3 = [1,2,3]
k3 = 4 # 4%3=1
solution.rotate(nums3, k3)
print("自定义测试1输出:", nums3) # 预期:[3,1,2]
# 自定义测试2(k=0)
nums4 = [10,20,30,40]
k4 = 0
solution.rotate(nums4, k4)
print("自定义测试2输出:", nums4) # 预期:[10,20,30,40]
4. Python代码运行过程(示例1一步步走)
输入:nums1 = [1,2,3,4,5,6,7],k1=3 → n=7,k=3,res = [0,0,0,0,0,0,0]
遍历原数组(i从0到6):
i=0(元素1):new_index=(0+3)%7=3 → res[3] = 1 → res = [0,0,0,1,0,0,0]
i=1(元素2):new_index=(1+3)%7=4 → res[4] = 2 → res = [0,0,0,1,2,0,0]
i=2(元素3):new_index=(2+3)%7=5 → res[5] = 3 → res = [0,0,0,1,2,3,0]
i=3(元素4):new_index=(3+3)%7=6 → res[6] = 4 → res = [0,0,0,1,2,3,4]
i=4(元素5):new_index=(4+3)%7=7%7=0 → res[0] = 5 → res = [5,0,0,1,2,3,4]
i=5(元素6):new_index=(5+3)%7=8%7=1 → res[1] = 6 → res = [5,6,0,1,2,3,4]
i=6(元素7):new_index=(6+3)%7=9%7=2 → res[2] = 7 → res = [5,6,7,1,2,3,4]
最后,nums[:] = res → 原数组nums1变成 [5,6,7,1,2,3,4](正确)。
5. C++代码(每句注释,能看懂)
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
void rotate(vector<int>& nums, int k) {
// 1. 获取数组长度n
int n = nums.size();
// 2. 简化k的值,避免无用轮转
k = k % n;
// 3. 新建一个和原数组等长的vector(新数组),初始值默认是0
vector<int> res(n);
// 4. 遍历原数组,把每个元素放到新数组的对应位置
for (int i = 0; i < n; i++) {
// 核心公式:新位置 = (原索引i + k) % n
int new_index = (i + k) % n;
// 把原数组i位置的元素,赋值给新数组new_index位置
res[new_index] = nums[i];
}
// 5. 把新数组res的内容复制回原数组nums(实现原地修改)
// nums = res:C++中,vector的赋值操作会直接替换原数组的内容
nums = res;
}
};
// 主函数(测试用例,和方法1一致)
int main() {
// 测试示例1
vector<int> nums1 = {1,2,3,4,5,6,7};
int k1 = 3;
Solution solution;
solution.rotate(nums1, k1);
cout << "示例1输出:";
for (int num : nums1) {
cout << num << " ";
}
cout << endl; // 预期:5 6 7 1 2 3 4
// 测试示例2
vector<int> nums2 = {-1,-100,3,99};
int k2 = 2;
solution.rotate(nums2, k2);
cout << "示例2输出:";
for (int num : nums2) {
cout << num << " ";
}
cout << endl; // 预期:3 99 -1 -100
// 自定义测试1(k大于n)
vector<int> nums3 = {1,2,3};
int k3 = 4;
solution.rotate(nums3, k3);
cout << "自定义测试1输出:";
for (int num : nums3) {
cout << num << " ";
}
cout << endl; // 预期:3 1 2
// 自定义测试2(k=0)
vector<int> nums4 = {10,20,30,40};
int k4 = 0;
solution.rotate(nums4, k4);
cout << "自定义测试2输出:";
for (int num : nums4) {
cout << num << " ";
}
cout << endl; // 预期:10 20 30 40
return 0;
}
6. 关键提醒(必看)
Python中,复制新数组到原数组,必须用 nums[:] = res,不能用 nums = res:
-
nums = res:是给nums重新赋值,指向新的列表,原数组(调用函数时传入的nums)不会被修改;
-
nums[:] = res:是给原数组的"每一个元素"重新赋值,直接修改原数组,符合原地修改要求。
C++中没有这个问题,nums = res 会直接替换原vector的内容,实现原地修改。
方法3:三次反转法(最优解,O(1)空间,面试必考)
1. 核心思路(神仙技巧,慢慢看,结合例子就懂)
我们先想一个问题:向右轮转k步,本质是"把数组的最后k个元素,搬到数组的最前面",比如示例1:
原数组:[1,2,3,4,5,6,7] → 最后3个元素:[5,6,7],前4个元素:[1,2,3,4] → 轮转后:[5,6,7,1,2,3,4]
三次反转法就是用"反转数组"的操作,实现这个效果,而且不需要新建数组(空间复杂度O(1)),核心步骤3步:
① 反转「整个数组」;
② 反转「前k个元素」;
③ 反转「后n-k个元素」。
示例1演示(一步步来,必看):
原数组:[1,2,3,4,5,6,7],k=3,n=7
步骤1:反转整个数组 → [7,6,5,4,3,2,1]
步骤2:反转前k=3个元素 → 反转[7,6,5] → [5,6,7,4,3,2,1]
步骤3:反转后n-k=4个元素 → 反转[4,3,2,1] → [5,6,7,1,2,3,4](最终结果,正确!)
为什么这样可行?大白话解释:
-
反转整个数组:把最后k个元素(5,6,7)转到了前面,但顺序是反的(变成7,6,5);
-
反转前k个元素:把7,6,5改成5,6,7(恢复正确顺序);
-
反转后n-k个元素:把原来的前4个元素(1,2,3,4),在反转整个数组后变成4,3,2,1,反转后恢复1,2,3,4(正确顺序)。
2. 复杂度分析
-
时间复杂度:O(n)(三次数组反转,每次反转的元素个数加起来是n + k + (n-k) = 2n,近似为n,效率和额外数组法一样);
-
空间复杂度:O(1)(仅用2个指针变量,不新建数组,纯原地修改,是面试中最推荐的解法)。
3. Python代码(每句注释,能看懂)
python
class Solution:
def rotate(self, nums: list[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
注释:原地修改,不返回任何值
"""
# 1. 获取数组长度n
n = len(nums)
# 2. 简化k的值,避免无用轮转(核心操作,不能少)
k = k % n
# 3. 定义一个反转函数:反转nums中[left, right]区间的元素(左闭右闭)
# left:反转的起始索引,right:反转的结束索引
def reverse(left: int, right: int) -> None:
# 循环条件:左指针left < 右指针right(当left >= right时,反转完成)
while left < right:
# 交换left和right指向的元素(Python中可以直接这样交换,不用临时变量)
nums[left], nums[right] = nums[right], nums[left]
# 左指针向右移1位,右指针向左移1位
left += 1
right -= 1
# 4. 三次反转(核心步骤)
reverse(0, n-1) # 步骤1:反转整个数组(从索引0到n-1)
reverse(0, k-1) # 步骤2:反转前k个元素(从索引0到k-1)
reverse(k, n-1) # 步骤3:反转后n-k个元素(从索引k到n-1)
# 主函数(测试用例,验证结果)
if __name__ == "__main__":
# 测试示例1
nums1 = [1,2,3,4,5,6,7]
k1 = 3
solution = Solution()
solution.rotate(nums1, k1)
print("示例1输出:", nums1) # 预期:[5,6,7,1,2,3,4]
# 测试示例2
nums2 = [-1,-100,3,99]
k2 = 2
solution.rotate(nums2, k2)
print("示例2输出:", nums2) # 预期:[3,99,-1,-100]
# 自定义测试1(k大于n)
nums3 = [1,2,3]
k3 = 4 # 4%3=1
solution.rotate(nums3, k3)
print("自定义测试1输出:", nums3) # 预期:[3,1,2]
# 自定义测试2(k=0)
nums4 = [10,20,30,40]
k4 = 0
solution.rotate(nums4, k4)
print("自定义测试2输出:", nums4) # 预期:[10,20,30,40]
4. Python代码运行过程(示例1一步步走)
输入:nums1 = [1,2,3,4,5,6,7],k1=3 → n=7,k=3
第一步:调用reverse(0, 6)(反转整个数组)
left=0,right=6 → 交换nums[0]和nums[6] → [7,2,3,4,5,6,1] → left=1,right=5
left=1,right=5 → 交换nums[1]和nums[5] → [7,6,3,4,5,2,1] → left=2,right=4
left=2,right=4 → 交换nums[2]和nums[4] → [7,6,5,4,3,2,1] → left=3,right=3(循环结束)
第二步:调用reverse(0, 2)(反转前3个元素)
left=0,right=2 → 交换nums[0]和nums[2] → [5,6,7,4,3,2,1] → left=1,right=1(循环结束)
第三步:调用reverse(3, 6)(反转后4个元素)
left=3,right=6 → 交换nums[3]和nums[6] → [5,6,7,1,3,2,4] → left=4,right=5
left=4,right=5 → 交换nums[4]和nums[5] → [5,6,7,1,2,3,4] → left=5,right=4(循环结束)
最终数组:[5,6,7,1,2,3,4](正确)。
5. C++代码(每句注释,能看懂)
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
// 3. 定义反转函数:反转nums中[left, right]区间的元素(左闭右闭)
// 参数:nums是原数组(引用传递,直接修改),left起始索引,right结束索引
void reverse(vector<int>& nums, int left, int right) {
// 循环条件:左指针left < 右指针right(left >= right时结束)
while (left < right) {
// 交换left和right指向的元素(C++的swap函数,直接交换两个值)
swap(nums[left], nums[right]);
// 左指针右移1位,右指针左移1位
left++;
right--;
}
}
// 主函数:轮转数组
void rotate(vector<int>& nums, int k) {
// 1. 获取数组长度n
int n = nums.size();
// 2. 简化k的值,避免无用轮转
k = k % n;
// 4. 三次反转(核心步骤)
reverse(nums, 0, n-1); // 步骤1:反转整个数组(索引0到n-1)
reverse(nums, 0, k-1); // 步骤2:反转前k个元素(索引0到k-1)
reverse(nums, k, n-1); // 步骤3:反转后n-k个元素(索引k到n-1)
}
};
// 主函数(C++程序入口,用于测试代码,和前面两种方法结构一致)
int main() {
// 测试示例1(题目给的第一个案例)
vector<int> nums1 = {1,2,3,4,5,6,7}; // 定义C++数组(vector)
int k1 = 3;
Solution solution; // 创建Solution类对象,用于调用rotate和reverse函数
solution.rotate(nums1, k1); // 调用rotate函数,原地修改nums1
cout << "示例1输出:";
for (int num : nums1) { // 遍历数组,打印每个元素
cout << num << " ";
}
cout << endl; // 换行,预期输出:5 6 7 1 2 3 4
// 测试示例2(题目给的第二个案例)
vector<int> nums2 = {-1,-100,3,99};
int k2 = 2;
solution.rotate(nums2, k2);
cout << "示例2输出:";
for (int num : nums2) {
cout << num << " ";
}
cout << endl; // 预期输出:3 99 -1 -100
// 自定义测试用例1(k大于数组长度,测试取模的作用)
vector<int> nums3 = {1,2,3};
int k3 = 4; // n=3,k3%3=1,等价于轮转1步
solution.rotate(nums3, k3);
cout << "自定义测试1输出:";
for (int num : nums3) {
cout << num << " ";
}
cout << endl; // 预期输出:3 1 2
// 自定义测试用例2(k=0,无需轮转,测试边界情况)
vector<int> nums4 = {10,20,30,40};
int k4 = 0;
solution.rotate(nums4, k4);
cout << "自定义测试2输出:";
for (int num : nums4) {
cout << num << " ";
}
cout << endl; // 预期输出:10 20 30 40
return 0; // 主函数结束,返回0表示程序正常运行
}
6. C++代码运行过程(示例1一步步走,必看)
输入:nums1 = {1,2,3,4,5,6,7},k1=3 → n=7,k=3(无需简化)
第一步:调用reverse(nums1, 0, 6)(反转整个数组,索引0到6)
left=0(指向1),right=6(指向7)→ 调用swap(nums1[0], nums1[6]) → 数组变成 {7,2,3,4,5,6,1} → left=1,right=5
left=1(指向2),right=5(指向6)→ 调用swap(nums1[1], nums1[5]) → 数组变成 {7,6,3,4,5,2,1} → left=2,right=4
left=2(指向3),right=4(指向5)→ 调用swap(nums1[2], nums1[4]) → 数组变成 {7,6,5,4,3,2,1} → left=3,right=3(left >= right,循环结束,反转完成)
第二步:调用reverse(nums1, 0, 2)(反转前k=3个元素,索引0到2)
left=0(指向7),right=2(指向5)→ 调用swap(nums1[0], nums1[2]) → 数组变成 {5,6,7,4,3,2,1} → left=1,right=1(循环结束,反转完成)
第三步:调用reverse(nums1, 3, 6)(反转后n-k=4个元素,索引3到6)
left=3(指向4),right=6(指向1)→ 调用swap(nums1[3], nums1[6]) → 数组变成 {5,6,7,1,3,2,4} → left=4,right=5
left=4(指向3),right=5(指向2)→ 调用swap(nums1[4], nums1[5]) → 数组变成 {5,6,7,1,2,3,4} → left=5,right=4(循环结束,反转完成)
最终数组:{5,6,7,1,2,3,4}(和预期结果一致,正确!)
7. C++代码调用流程(必看)
① 引入头文件:iostream(用于cout打印结果)、vector(用于定义C++数组),使用namespace std简化代码,不用每次写std::前缀;
② 定义Solution类:包含两个函数------reverse函数(用于反转数组指定区间)和rotate函数(核心轮转逻辑);
③ reverse函数调用逻辑:接收原数组(引用传递)、起始索引left、结束索引right,通过双指针交换元素,实现区间反转;
④ rotate函数调用逻辑:先获取数组长度n,简化k的值,再依次调用3次reverse函数,完成三次反转,实现原地轮转;
⑤ main函数(程序入口)调用逻辑:
a. 定义4组测试用例(示例1、示例2、自定义测试1、自定义测试2),分别定义数组和k值;
b. 创建Solution类对象solution;
c. 调用solution.rotate(数组, k),通过引用传递直接修改原数组;
d. 用for循环遍历数组,打印每个元素,查看轮转结果是否符合预期;
e. main函数返回0,程序正常结束。
四、总结
1. 必做核心操作(所有方法都要加)
无论用哪种方法,第一步必须是 k = k % n(n是数组长度),目的是简化k的值,避免无用轮转(比如k=10、n=7,轮转10步和3步效果一样,省掉7步无用操作)。
2. 三种方法对比(选方法指南)
| 方法 | 核心特点 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|---|
| 暴力逐次轮转法 | 最简单、最易理解,逐次轮转1步,重复k次 | O(n×k) | O(1) | 入门、理解题意(面试/开发绝对不用) |
| 额外数组法 | 空间换时间,直接计算元素最终位置,高效易懂 | O(n) | O(n) | 面试入门、日常开发(简单高效,不易出错) |
| 三次反转法 | 最优解,纯原地修改,无额外空间,神仙技巧 | O(n) | O(1) | 面试必考、高频考点(必须掌握,加分项) |
3. 学习建议
① 先学暴力法:不用追求效率,重点理解"向右轮转"的本质,搞懂元素后移、最后一个元素前置的逻辑;
② 再学额外数组法:掌握"新位置 = (原索引 + k) % n"的核心公式,理解"空间换时间"的思路;
③ 最后攻克三次反转法:多手动演示反转步骤,记住"三次反转"的顺序(整个数组→前k个→后n-k个),理解为什么这样能实现轮转;
④ 多运行代码:把每段代码复制到编译器(Python用IDLE、C++用Dev-C++),修改测试用例,观察运行结果,加深理解。
4. 常见坑点提醒(必避)
① 索引从0开始:不要把索引当成"第几个元素",比如第1个元素索引是0,第7个元素索引是6;
② 原地修改的误区:Python中用nums[:] = res,不要用nums = res;C++中用引用传递(vector<int>& nums),不要漏写"&";
③ 忘记简化k:如果k大于n,不做k = k % n,会导致大量无用操作,代码效率极低;
④ 反转函数的边界:reverse函数是"左闭右闭"区间(比如reverse(0,2),包含索引0、1、2三个元素),不要漏写或多写索引。