news 2025/12/23 4:37:24

【码道初阶】Leetcode.189 轮转数组:不熟悉ArrayList时踩得坑,被Arraylist初始化骗了?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【码道初阶】Leetcode.189 轮转数组:不熟悉ArrayList时踩得坑,被Arraylist初始化骗了?

【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. 我的“完美”思路与报错代码

我的第一反应非常直观:

  1. 创建一个新的列表list
  2. 遍历原数组,计算每个元素移动k步后的新位置。
  3. 如果位置超出了数组长度,就绕回到开头;否则直接放入新位置。
  4. 使用ArrayListset方法填值。

于是我写出了以下代码:

// ❌ 错误示范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)空间复杂度下完成?即不使用额外数组?”

这需要用到**“三次翻转法”**(非常巧妙):

  1. 翻转整个数组。
  2. 翻转前k % n个元素。
  3. 翻转剩余的元素。

示例[1,2,3,4,5,6,7],k=3

  1. 全翻转:[7,6,5,4,3,2,1]
  2. 翻转前3个:[5,6,7, 4,3,2,1]
  3. 翻转后4个:[5,6,7, 1,2,3,4]->成功!

7. 总结

这次排错让我学到了三点:

  1. API 误区new ArrayList<>(capacity)只是申请内存,不代表 List 里有元素。想按索引操作,优先选数组,或者先填充 List。
  2. 数学之美:处理循环数组索引时,%取模运算比if-else判断更优雅通用。
  3. 审题重要性:注意函数的返回类型和副作用要求(是返回新对象,还是原地修改)。

编程就是这样一个不断犯错、理解底层、然后优化的过程。希望能帮到遇到同样报错的你!


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!