一个简单的问题我糊涂了很久才搞好,这是我的一些解题思路,希望大神们指点,指导我更好的解答。
这是一个数组原地去重的问题,最多保留k个重复元素,然后返回原地去重后新数组的大小。
我的思路是这么发展的:
原文链接如下:http://t.csdn.cn/8tU72
LeetCode26:给你一个有序数组nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。不要使用额外的数组空间,你必须原地修改数组,在空间复杂度O1的情况下完成。
这个题的思路在于需要保存重复元素的值。如果我能保存重复元素的值,那么负责遍历数组的元素就能做出判断,如果是重复元素值就继续前进,直到不是该值为止。在加上算法通关村这章是双指针,所以这里我使用了双指针:
slow代表去重后的有效元素,[0,slow]是去重的结果区间;fast用于遍历数组。
如下图所示,刚开始slow==fast==0,然后fast不断前进直到不是该重复值(此处为1)为止。然后slow++,并将fast遍历到的新值赋给arr[slow],这样arr[fast]根据是否==arr[slow]继续前进,直到不是arr[slow]的值为止。此时arr[fast]就是一个新值,再赋给arr[++slow]。这样每有一个新的不同值就存储在[0,slow]区间中,从而实现了去重。
这类问题要处理边界情况,用for循环是更好的。
public static int deleteDuplicate(int[] arr){
int slow = 0;
for (int fast = 0; fast < arr.length; fast++) {
if (arr[fast] != arr[slow]){
slow++;
arr[slow] = arr[fast];
}
//每次循环fast都前进一步,如果不等于arr[slow](找到新值)了,就赋值给arr[++slow]
//如果等于arr[slow](是重复值)了,那么slow不做任何改变,fast继续++判断下一个元素
}
return slow + 1;
}
可以看到,每次循环fast只走一步,最后退出循环时刚好是arr.length-1索引,就不用纠结越界问题了,因为该for循环中不会出现越界。
但如果每个元素可以出现一次或两次,也就是最多可以出现两次,又该如何应对呢? (LeetCode80)例:输入【1,1,1,2,2,3】 输出【1,1,2,2,3】
我认为这类问题的核心实际上是:fast指针在遍历数组时,判断哪些值可以放进[0,slow]结果区间。
在前面的去重问题中,只有当arr[fast] != arr[slow]时,arr[fast]才能放入arr[++slow]中,这样才能确保重复元素只保留一个。
那么最多保留2个的时候,哪些值可以放进结果区间呢?
答案是:1.新值,即arr[fast] != arr[slow]的可以放入[0,slow]的区间。
2.重复值的第二个元素,即arr[fast] == arr[slow],但是arr[fast]是第二个重复值时,这种情况也可以放入[0,slow]区间。如下图,fast遍历至第二个2时arr[slow]已经==2,但这个2是可以加入[0,slow]区间的。

以此类推,如果最多保留K个元素,那么这些值可以放进结果区间:是新值的,和是第2~第K个重复值的。第K+1个重复值及以上不能放入结果区间。
那么现在我们注重编码的点,就落在了如何确定当前元素是第二到第K个重复元素上。
思路一:arr[fast-k] != arr[fast] 有何不妥?
不瞒大家,我最开始想到的其实是这个。也许你也这么想,如果限制只能是第二到第K个元素,那么因为第二到第K个重复值的元素,往前K个就是原数组中不同值的元素了,两者值不等;而第K+1个元素,往前K个就是第一个重复元素,值相等。所以限制条件应该是:
arr[slow] != arr[fast] || fast < k || (arr[slow] == arr[fast] && arr[fast-k] != arr[fast] )
乍一看,这不是很对吗?
但是问题在这:经过对arr[slow]的赋值,[0,slow]区间的元素已经发生改变,不再是有序的。此时如果还认为第K个重复值元素往前K个的值是不同的,可能会出问题。
如下图,是最多保留2个重复元素(k==2)的情况,里面就出现了问题。

如图,k==2,所以第三个2被舍弃了,同时fast移动到第一个3,是新值,所以把原来第三个2的位置(slow所指的位置)赋值成了3。你发现了没有,3的个数改变了。此时原本的第2个3,也就是第二张图中fast指向的元素,居然变成《第3个》3了。再进行arr[fast-k] != arr[fast] 时,就离谱地判false了,而它本应该加入[0,slow]结果区间的。
这就是原地修改的弊病:给arr[slow]赋值时修改了原数组前面的值,使前面的元素偏离了初始值。
所以我自己总结出一个结论:在进行双指针的原地修改时,fast指针不能回头看。
也就是不能用arr[fast-k]的意思,不知“回头”这两个字你是否理解透彻了呢?
思路二:正确做法是什么?
如果fast不能回头的话,从一开始我们就不应该往后看,而是应该设定一个变量,记录当前元素是重复值的第几个。代码如下:
public static int deleteDuplicateButSaveMostK(int[] arr,int k){
int slow = 0;
int repeatCount = 1;
//repeatCount是当前值的重复次数,我们从第二个元素开始遍历,此时第一个元素重复次数为1次。
//第一个元素可看做新值,看做已放入[0,slow]结果区间中,此时slow == 0指向该新值
for (int fast = 1; fast < arr.length; fast++) {//fast==1,从第二元素开始遍历
if (arr[fast] != arr[slow]){//arr[fast]是新值就将当前值重复次数置为1次,然后赋值
repeatCount = 1;
arr[++slow] = arr[fast];
}else {
//是重复值就把重复次数++,如果重复次数<=k就赋给arr[++slow]
repeatCount++;
if (repeatCount <= k){
arr[++slow] = arr[fast];
}
//如果重复次数>=k+1就不做处理,fast继续++直到找到新值为止。
}
}
return slow+1;
}
最多保留重复元素个数,从1到K,全部可以用这个方法处理。
菜鸟初出茅庐,希望大神们指点更简单的做法。
