小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
话说最近想到了一种新的方法解决leetcode 26. 删除有序数组中的重复项 这一题。
题干是给我们一个已经排好序的数组,让我们想出一种方法,在不依赖额外的数组 的情况下,原地 对数组做去重操作。
当然它的要求实际上是逻辑意义上的删除,也就是只要在去重之后,返回回逻辑意义上的末尾位置就可以了(后面还有元素相当于是当它不存在)。
我想从借助一个辅助的数组开始,假设用一个辅助数组来去重的话。这样的话,我们要做的事就是,每次碰到一个新的值的时候,我们把他放在辅助数组里。而所谓的新的值它其实就是和前一个值来比。因为数组已经是有序的了,相同的元素它们就必定会是连续的,而我们的辅助数组它的末尾总是会是最新的值,和它做比较决定是否放入辅助数组里即可。不过还有一种例外的情况,对于数组的第一个元素,他是没有所谓的前一个值的,但是第一个元素一定不是重复值,我们可以直接把它放置到辅助数组的开头就可以了。依照这个思路,我们可以写出这样的代码:
fun removeDuplicates(nums: IntArray): Int {
val helper = IntArray(nums.size) { 0 }
var j = -1
nums.forEachIndexed { i, v ->
when {
j == -1 -> {
helper[++j] = v
}//首个元素复制到辅助数组
nums[i] != helper[j] -> {
helper[++j] = v
}//如果是全新的值则复制到辅助数组
}
}
return j + 1
}
如果按上面的代码解这个题其实还差了一步,最后判定结果是以给定长度割断数组,判断是否去重。所以还需要在return的前一行写一个复制操作,把辅助数组复制到原数组中去。
不过这里肯定不能这么做,这样它不符合题意(不可以使用额外的数组)。
所以我们再仔细观察观察上述的代码,看看怎么让这个辅助数组消失,同时我们少做一些更改(也就是说以近乎相同的方式去完成这个去重操作)。
我们把视线放到对nums的每一个元素的操作上去。对与每一个下标和它对应的元素,在它之前的的数据都已经被处理过了,或者说是无关的了,只有它和它之后还没有处理的元素才是不能动的。
更重要的一点是,i总是会在j的前面 ,i ≥ j这个等式总是成立的。
而这样我们就有文章可以做了,在每一个复制操作之前,所谓的helper辅助数组其实只需要j的长度,而原数组的这一段是可以被使用的。换句话说,在这里我们可以用nums这一段不使用的区间来假装 自己有一个辅助用的数组,实际上就是它本身。
具体到代码上我们可以这样仅仅改动helper的值,让它和nums本质上是同一个数组。
fun removeDuplicates(nums: IntArray): Int {
val helper = nums //假装自己有一个辅助数组
var j = -1
nums.forEachIndexed { i, v ->
when {
j == -1 -> {
helper[++j] = v
}//首个元素复制到辅助数组
nums[i] != helper[j] -> {
helper[++j] = v
}//如果是全新的值则复制到辅助数组
}
}
return j + 1
}
如果不喜欢这个额外的helper,也可以直接把他替换成nums然后去掉helper的变量定义。
这个思路,其实是我在写给有序数组分组的程序时想到的。只是分组的方法其实比这里还要容易些,它不用原地做处理。甚至它都不需要关注下标的变化,例如对升序整数数组排序可以像下面这样:
fun groupBy(x: Array<Int>): ArrayList<ArrayList<Int>> {
val res = ArrayList<ArrayList<Int>>()
x.forEach {
when {
res.isEmpty() -> {
res.add(ArrayList())
res.last().add(it)
}
res.last().first() == it -> {
res.last().add(it)
}
else -> {
res.add(ArrayList())
res.last().add(it)
}
}
}
return res
}
其实这个分组和我们的去重,本质上是一样的。但是,这种从一个更简单的情况逐渐到复杂一些的问题,我个人认为更符合我们的思维方式。如果把我们的解法与双指针的解法对比的话,其实会发现他们也是殊途同归。如果有不同看法,欢迎讨论。