【LeetCode 踩坑日记】No.189 轮转数组:被 ArrayList 的初始化骗了?
在刷算法题的过程中,我们经常会遇到一种情况:思路明明是正确的,逻辑也很通顺,但代码一跑就崩,或者结果不对。
今天我在做LeetCode 189. 轮转数组时,就遭遇了一个经典的 Java 集合陷阱。这篇博客将记录我从“理直气壮”到“恍然大悟”的排错过程,并总结关于数组轮转的通用解法。
1. 题目回顾
题目:给定一个整数数组nums,将数组中的元素向右轮转k个位置。
示例:
输入:nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]
2. 我的“完美”思路与报错代码
我的第一反应非常直观:
- 创建一个新的列表
list。 - 遍历原数组,计算每个元素移动
k步后的新位置。 - 如果位置超出了数组长度,就绕回到开头;否则直接放入新位置。
- 使用
ArrayList的set方法填值。
于是我写出了以下代码:
// ❌ 错误示范classSolution{publicvoidrotate(int[]nums,intk){// 我以为这里创建了一个长度为 nums.length 的列表List<Integer>list1=newArrayList<>(nums.length);for(inti=0;i<nums.length;i++){// 这里逻辑其实有点硬编码,把 k 当成了 3if(i+3>nums.length-1){list1.set(i+3-nums.length,nums[i]);}else{list1.set(i+3,nums[i]);}}System.out.println(list1);}}运行结果:
直接抛出java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 0。
3. 深度解析:为什么会报错?
看到报错我非常困惑:我在new ArrayList<>(nums.length)时不是已经指定长度了吗?为什么报错说length 0?
这里触及到了 JavaArrayList的一个核心机制:Capacity(容量) vs Size(元素个数)。
- Capacity(容量):是你告诉底层数组“请预留多少空间”。
- Size(实际大小):是列表里实际上装了多少个有效元素。
当我们执行new ArrayList<>(10)时,Java 只是在内存里开辟了一个能放10个东西的“空架子”,但是架子上还没有书,此时size依然是 0。
add()方法:是在列表尾部追加元素,会使size + 1。set(index, element)方法:是修改现有位置的元素。它会检查index是否小于size。
因为我的列表是空的(Size = 0),任何set(index, val)操作都会被判定为越界,因为那个位置上根本还没有元素可以被“修改”。
修正思路:
如果想用“指定位置填值”的逻辑,应该直接使用数组 (int[]),因为数组在创建时(new int[n]),所有位置默认已经初始化为 0,长度是固定的,可以直接通过下标赋值。
4. 逻辑上的二次修正
除了报错,我的代码逻辑还有两个问题需要解决才能通过 LeetCode 的测试:
A. 变量k的处理
我之前的代码里写死了i + 3,这是针对示例 1 的特解。题目中的k是变量,且k可能大于数组长度。
改进:使用取模运算符%。newIndex = (i + k) % n
这行公式完美解决了越界和绕回的问题,不需要写if-else判断。
B. 引用修改 vs 打印
题目方法的返回类型是void。这意味着仅仅System.out.println是没用的,LeetCode 的判题系统会检查内存中nums数组的内容是否发生了变化。
改进:必须把计算出的新结果,重新拷贝回原数组nums。
5. 最终正确代码(辅助数组法)
经过思考,我放弃了ArrayList,改用辅助数组,代码变得清晰且高效:
// ✅ 正确解答classSolution{publicvoidrotate(int[]nums,intk){intn=nums.length;// 1. 创建一个同样长度的新数组int[]newArr=newint[n];// 2. 计算新位置并赋值for(inti=0;i<n;i++){// 使用 (i + k) % n 处理循环移动newArr[(i+k)%n]=nums[i];}// 3. 将新数组的值拷贝回原数组// System.arraycopy(newArr, 0, nums, 0, n); // 也可以用这个APIfor(inti=0;i<n;i++){nums[i]=newArr[i];}}}6. 进阶思考:能不能不费额外空间?
上面的解法使用了O(N)的额外空间(创建了newArr)。如果面试官问:“能不能在O(1)空间复杂度下完成?即不使用额外数组?”
这需要用到**“三次翻转法”**(非常巧妙):
- 翻转整个数组。
- 翻转前
k % n个元素。 - 翻转剩余的元素。
示例:[1,2,3,4,5,6,7],k=3
- 全翻转:
[7,6,5,4,3,2,1] - 翻转前3个:
[5,6,7, 4,3,2,1] - 翻转后4个:
[5,6,7, 1,2,3,4]->成功!
7. 总结
这次排错让我学到了三点:
- API 误区:
new ArrayList<>(capacity)只是申请内存,不代表 List 里有元素。想按索引操作,优先选数组,或者先填充 List。 - 数学之美:处理循环数组索引时,
%取模运算比if-else判断更优雅通用。 - 审题重要性:注意函数的返回类型和副作用要求(是返回新对象,还是原地修改)。
编程就是这样一个不断犯错、理解底层、然后优化的过程。希望能帮到遇到同样报错的你!