Scala中如何修改传入方法的数组引用?附LeetCode移除元素场景
问题场景(LeetCode移除元素问题)
给定数组nums和目标值target,需要原地移除所有等于target的元素并返回新长度,要求使用O(1)额外空间,不能分配新数组。举个实际例子:
输入:
nums = [0,1,2,2,3,0,4,2],target=2
要求修改后数组为[0,1,3,0,4],返回长度5
实现思路
- 识别所有等于
target的元素,将其移至数组右侧并统计数量; - 保留数组左侧的有效元素(非
target值); - 返回原数组长度减去目标元素的统计数,即为有效元素的新长度。
现有代码与问题分析
先看你编写的Scala代码:
object RemoveElement { // Link to question: https://leetcode.com/problems/remove-element/ def main(args: Array[String]): Unit = { var nums = Array(3,2,2,3) val target = 3 val result = removeElement(nums, target) // nums = nums.dropRight(_.equals(target)) // POINT 1 println(s"Result: ${result}, Modified Array: [${nums.mkString(", ")}]") } def removeElement(nums: Array[Int], target: Int): Int = { val n = nums.length var left, counter = 0 var right = n - 1 while(left < right){ if(nums(left) != target){ left += 1 } else { // Find position of the elements which is not equal to target if(nums(right) == target){ counter += 1 right -= 1 } else{ // Swap the elements counter += 1 val temp = nums(left) nums(left) = nums(right) nums(right) = temp left += 1 right -= 1 } } } // nums.dropWhile(_.equals(target)) // POINT 2 // nums = nums.dropRight(_.equals(target)) // POINT 3 return (n - counter) } }
针对代码里的几个关键点,我来拆解分析:
- POINT 1:
main方法里修改nums是可行的,因为这里的nums是var类型,允许重新赋值。但dropRight的用法有误——它需要接收一个整数表示移除的数量,而非谓词,正确写法应该是nums = nums.dropRight(counter)。 - POINT 2:这条语句对原数组无任何影响,因为
dropWhile会返回一个新数组,而你没有将返回值赋值给任何变量,原数组nums(方法参数里的val)不会被修改。 - POINT 3:这里报错是因为
removeElement方法里的nums是val类型——Scala方法参数默认是val,虽然数组本身是可变的(可修改内部元素),但你不能给这个val参数重新赋值。
核心疑问解答:Scala中如何原地修改传入的集合并让调用方感知?
要解决这个问题,得先理清Scala集合的可变/不可变特性,以及方法参数的规则:
1. 数组的特殊性:可变容器,但参数默认是val
数组(Array[T])本身是可变的——哪怕方法参数是val类型,你依然可以修改数组任意索引位置的元素值。但你不能给这个val参数重新赋值(比如nums = 新数组),这是语法层面的限制。
2. 满足LeetCode要求的正确做法
LeetCode的“原地修改”核心要求是:确保数组前N个元素为有效元素(N是返回的长度),不需要真的“删除”后面的元素(判题时只会检查前N个元素是否正确)。如果想让调用方看到的数组只包含有效元素,有两种方式:
方式一:调用方截断数组
你的现有代码已经把所有非target元素移到了数组左侧,只是后面还残留target元素。此时在main方法里,用返回的长度截断数组即可:
// main方法里修改这一行: nums = nums.take(result)
take(result)会返回原数组前result个元素组成的新数组,赋值给var类型的nums后,输出的就是正确的有效数组。
方式二:仅调整元素位置(满足LeetCode判题)
其实LeetCode并不要求你真的删除数组尾部元素,只要前result个元素是正确的非target值即可。你的交换逻辑已经实现了这一点——比如测试用例nums = [3,2,2,3],target=3,运行后数组会变成[2,2,3,3],返回的result是2,LeetCode判题时只会检查前2个元素[2,2],完全符合要求。
3. 通用场景:修改传入的集合
- 对于可变集合(比如
scala.collection.mutable.ArrayBuffer),即使方法参数是val,你也可以直接调用它的可变方法(如remove、add)修改集合内容,调用方会感知到变化,因为可变集合是引用类型,方法操作的是同一个实例。 - 对于不可变集合(比如
List、Vector),你无法修改原集合,只能返回一个新集合,让调用方重新赋值。
修正后的完整代码
这里给出修正后的代码,既满足LeetCode判题要求,又能在main方法中输出正确的数组:
object RemoveElement { def main(args: Array[String]): Unit = { var nums = Array(3,2,2,3) val target = 3 val result = removeElement(nums, target) nums = nums.take(result) // 截断数组到有效长度 println(s"Result: ${result}, Modified Array: [${nums.mkString(", ")}]") } def removeElement(nums: Array[Int], target: Int): Int = { val n = nums.length var left = 0 var counter = 0 var right = n - 1 while(left < right){ if(nums(left) != target){ left += 1 } else { if(nums(right) == target){ counter += 1 right -= 1 } else{ // 交换元素,把非target元素移到左边 val temp = nums(left) nums(left) = nums(right) nums(right) = temp counter += 1 left += 1 right -= 1 } } } // 最后检查中间的元素(当left == right时) if(left == right && nums(left) == target){ counter +=1 } n - counter } }
注意我补充了最后一步检查:当left和right相遇时,如果该位置元素是target,需要计入计数器,避免漏算这种边界情况。
内容的提问来源于stack exchange,提问作者Kumar Rohit

