力扣解题-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 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105
进阶:
尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
Related Topics 数组、数学、双指针
第一种解答(超时)
解题思路
核心方法:逐次右移模拟,通过嵌套循环模拟“每次右移1步,共移k步”的过程,每一步都将数组元素逐个右移,最后将最后一个元素放到首位,逻辑直观但时间复杂度极高,无法适配大数据量。
具体步骤:
- 初始化临时变量:
temp(暂存当前元素)、write(待写入的元素值)、length(数组长度); - 外层循环控制轮转次数:循环k次,每次实现“右移1步”;
- 内层循环实现单次右移:
- 从索引1开始遍历数组,先暂存前一个位置的元素(
write); - 将当前位置的元素暂存到
temp,再把write写入当前位置; - 遍历到最后一个元素时,将
temp(最后一个元素)写入数组首位;
- 从索引1开始遍历数组,先暂存前一个位置的元素(
- 无返回值:直接修改原数组。
超时原因说明
该解法在数据量较大时(如nums.length=10⁵、k=10⁵)会直接超时,核心问题:
- 时间复杂度爆炸:嵌套循环的时间复杂度为O(k×n),当k和n均为10⁵时,总操作数达到10¹⁰,远超力扣时间限制(通常1秒仅能处理10⁸次操作);
- 逻辑冗余:逐次右移完全没必要,可通过数学计算直接定位元素最终位置,无需重复遍历;
- 无性能数据(超时):该思路仅适用于k和n极小的场景,完全不满足题目数据规模要求。
public void rotate(int[] nums, int k) {
int temp=0;
int write=0;
int length=nums.length;
for(int i=0;i<k;i++){
for(int j=1;j<length;j++){
if(j==1) {
write = nums[j - 1];
}else {
write=temp;
}
temp=nums[j];
nums[j] = write;
if(j+1==length){
nums[0] = temp;
}
}
}
}
第二种解答
解题思路
核心方法:哈希表映射位置,通过HashMap记录“原索引元素”对应的“目标索引位置”,再遍历哈希表将元素写回原数组,利用哈希表实现位置映射,但存在额外的哈希操作开销。
核心原理铺垫
右轮转k个位置的数学规律:原索引为i的元素,轮转后的目标索引为(i + k) % length(取模避免索引越界,适配k>length的场景)。
具体步骤:
- 边界快速处理:若数组长度<2,无需轮转,直接返回;
- 初始化哈希表:
Map<Integer, Integer>,键为目标索引,值为对应元素; - 遍历映射位置:遍历数组每个元素,计算其目标索引
end = (i + k) % length,将(end, nums[i])存入哈希表; - 写回原数组:遍历哈希表的键(目标索引),将对应值写入nums的指定索引位置,完成轮转。
性能劣势说明
该解法耗时15ms仅击败3.10%用户、内存66.33MB仅击败5.18%用户,核心问题:
- 哈希表的额外开销:
put和get操作涉及哈希计算、冲突处理,且遍历keySet的顺序不确定(但不影响结果),时间复杂度虽为O(n),但常数因子极大; - 空间复杂度高:哈希表需存储n个键值对,空间复杂度O(n),且哈希表的存储结构(数组+链表/红黑树)比普通数组更占内存;
- 逻辑冗余:无需通过哈希表映射,直接用数组即可完成位置存储,哈希表是“过度设计”。
public void rotate(int[] nums, int k) {
if(nums.length<2){
return;
}
int length=nums.length;
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int end = (i + k) % length;
map.put(end, nums[i]);
}
for(Integer key:map.keySet()){
nums[key]=map.get(key);
}
}
第三种解答
解题思路
核心方法:辅助数组暂存,利用额外数组存储元素的目标位置,再通过数组拷贝写回原数组,逻辑简洁且性能优于前两种,是空间换时间的经典思路。
核心原理铺垫
与第二种解答的位置映射逻辑一致,仅将哈希表替换为普通数组,避免哈希操作的额外开销:原索引i的元素 → 目标索引(i + k) % length。
具体步骤:
- 获取数组长度:
length = nums.length; - 创建辅助数组:
tempNums = new int[length],用于暂存轮转后的元素; - 填充辅助数组:遍历原数组,将
nums[i]写入tempNums[(i + k) % length]; - 写回原数组:调用
System.arraycopy(高效数组拷贝方法),将tempNums的所有元素拷贝到nums中,完成原地修改。
性能说明
该解法耗时1ms击败47.46%用户、内存61.9MB击败28.98%用户,是较优的“空间换时间”方案:
- 时间复杂度最优:仅两次遍历(填充辅助数组+拷贝),时间复杂度O(n),无额外冗余操作;
- 空间复杂度O(n):辅助数组占用n个元素的空间,不满足进阶要求的“O(1)原地算法”,但在工程中是易实现、易理解的方案;
System.arraycopy的优势:该方法是JVM底层优化的原生方法,拷贝效率远高于手动遍历赋值,是该解法性能优于第二种的核心原因。
public void rotate(int[] nums, int k) {
int length=nums.length;
int[] tempNums=new int[nums.length];
for(int i = 0; i < nums.length; i++){
int end = (i + k) % length;
tempNums[end]=nums[i];
}
System.arraycopy(tempNums,0,nums,0,length);
}
示例解答
解题思路
核心方法:三次反转法(原地算法,空间O(1)),利用数组反转的特性,通过三次反转实现无额外空间的右轮转,是本题的最优解(进阶要求的标准答案)。
核心原理铺垫
右轮转k个位置的本质:将数组后k个元素移到前面,前n-k个元素移到后面。通过反转可实现无额外空间的元素迁移:
- 先反转整个数组,打破原有顺序;
- 反转前k个元素,恢复后k个元素的正确顺序;
- 反转后n-k个元素,恢复前n-k个元素的正确顺序。
关键预处理:k = k % length(避免k>length时的无效轮转,如length=7、k=10,等价于k=3)。
具体步骤(补充最优解思路,代码未提供但适配题目进阶要求):
- 预处理k值:
k = k % nums.length(处理k≥数组长度的情况); - 反转整个数组:反转nums[0...length-1];
- 反转前k个元素:反转nums[0...k-1];
- 反转剩余元素:反转nums[k...length-1]。
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
reverse(nums, 0, n - 1); // 全翻转
reverse(nums, 0, k - 1); // 前 k 个翻转
reverse(nums, k, n - 1); // 后 n-k 个翻转
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
int tmp = nums[start];
nums[start] = nums[end];
nums[end] = tmp;
start++;
end--;
}
}
核心优势说明
- 时间复杂度O(n):三次反转的总操作数为n(反转n个元素),无冗余;
- 空间复杂度O(1):仅使用临时变量交换元素,完全满足进阶要求;
- 性能极致:无数组拷贝、哈希操作,是力扣该题的最优解法,耗时通常可击败99%以上用户。
总结
- 逐次右移法:逻辑直观但时间复杂度O(k×n),大数据量超时,仅适用于极小规模场景;
- 哈希表映射法:位置计算正确但哈希操作开销大,性能/内存均差,无实际使用价值;
- 辅助数组法:空间换时间的经典方案,时间O(n)、空间O(n),易实现且性能较好;
- 三次反转法:本题最优解,时间O(n)、空间O(1),满足进阶要求,是面试高频考点;
- 核心技巧:轮转问题优先通过“取模定位目标位置”或“反转”实现,避免逐次模拟的低效操作。