:-: 1.2 删除排序数组中的重复项 II
* * * * *
**题干:**
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
~~~
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
~~~
示例 2:
~~~
给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素为 0, 0, 1, 1, 2, 3, 3 。
你不需要考虑数组中超出新长度后面的元素。
~~~
说明:
为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
~~~
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
~~~
**题目分析:**
首先看题,题目的要求并不是删除元素的重复项,而是把排除元素三个及以上的重复项后的元素(重复项多于等于两个要两个重复项,小于两个大于零个直接保留)在有序排在数组前几个位置,接着有序不用考虑,题目给的数组本就有序(这个我们不能破坏,我们不给自己找麻烦),我们剩下的就要思考如何把排除元素三个及以上的重复项后的元素排在数组前几个位置。
**新手有可能遇到的解题思路陷阱:**
有的认为这个既然是删除排序数组中的重复项 II 它们的解法一定有关联(<——这个思路没错),而且这道题是数组的前几个位置是保留排除元素三个及以上的重复项后的元素,就将元素两个当作一个元素来考虑(当所取的两个元素和前两个元素不同就覆盖),同时位置变量两个递增,这样处理会造成有的该保留值被忽略,造成后面的值比较的时候混乱。
**解题思路分析以及代码实现:**
第一种思路:统计重复项,处理小二项,位置位递增,不同就覆盖(每检测到一个元素,我们需要统计下它的重复项有多少个,直到出现不同的元素,然后只处理一位,两位的元素,多余的元素不处理,位置位只有在处理元素是递增,遇到下一个不同的元素就按位置位覆盖)。
第一种思路代码:
~~~
public static int removeDuplicatesII(int[] nums) {
if(nums.length <= 2) return nums.length;
int pos = 1,flag = 1,int last = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] == last) {
flag++;
} else {
flag = 1;
}
if (flag <= 2) {
nums[pos] = nums[i];
pos++;
}
last = nums[i];
}
return pos;
}
//异曲同工之妙
public int removeDuplicates(int[] nums) {
if (nums.length <= 2) return nums.length;
int len = 1,cnt = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] != nums[len - 1]) {
cnt = 1;
nums[len++] = nums[i];
} else {
cnt++;
if (cnt > repeat) {
continue;
} else {
nums[len++] = nums[i];
}
}
}
return len;
}
~~~
**复杂度分析**
时间复杂度:O(n)。
空间复杂度:O(1)。
第二种思路:从首位元素位置开始,直到遇不同元素位值,两者位置想减大于二,说明中间多余可覆盖,接着从不同元素位值开始(从首位元素遍历检测,直到遇见不同的元素,记录两者位置相减,要是差值大于二,说明中间有多余的重复值,我们把不同元素位置开始的元素向覆盖,覆盖的初始位置位当前被检测位置+2,然后接着从不同元素的位置做初始位置,进行以上操作,直到循环结束)。
第二种思路代码:
~~~
public static int removeDuplicatesII(int[] nums) {
if (nums.length <= 2) return nums.length;
int step = 0, length = nums.length;
for (int i = 0; i < length; i++) {
step = i + 1;
for (; step < length; step++) {
if (nums[i] != nums[step]) {
break;
}
}
if (step - i > 2) {
int moveStep = step - i - 2;
for (int j = step; j < length; j++) {
nums[j - moveStep] = nums[j];
}
length -= moveStep;
}
}
return length;
}
~~~
**复杂度分析**
时间复杂度:O(n^2)。
空间复杂度:O(1)。
第三种思路:从首位开始检测,下位相同就处理,其他不考虑,遇到不同位,以上步骤继续(由题意得,我们只需要得到没有重复项的元素和有重复项元素的前两个,所以我们只需要处理需要得到的元素就可以了,记录下每次处理过后的位置,留作下次处理使用,直到循环结束)。
第三种思路代码:
~~~
public static int removeDuplicatesII(int[] nums) {
if (nums.length <= 2) return nums.length;
int i = 0, j = 0;
while (i < nums.length) {
nums[j] = nums[i];
j++;
if (i + 1 < nums.length && nums[i] == nums[i + 1]) {
nums[j] = nums[i + 1];
j++;
}
while (i + 1 < nums.length && nums[i] == nums[i + 1]) {
i++;
}
i++;
}
return j;
}
~~~
**复杂度分析**
时间复杂度:O(n^2)。
空间复杂度:O(1)。
**若有理解错误的、写错的地方、更好的思路,方法,望各位读者评论或者发邮件指正或指点我,不胜感激。**
- 前言
- 第一部分 初级入门算法
- 第一章 数组
- 1.1 删除排序数组中的重复项
- 1.2 删除排序数组中的重复项 II
- 1.3 买卖股票的最佳时机
- 1.4 买卖股票的最佳时机 II
- 1.5 移动零
- 1.6 区间子数组个数
- 1.7 搜索插入位置
- 1.8 合并两个有序数组
- 1.9 两个数组的交集
- 第二章 哈希表
- 2.1 两数之和
- 2.2 错误的集合
- 2.3 翻转卡片游戏
- 2.4 有效的字母异位词
- 第三章 链表
- 第四章 数学
- 4.1 加一
- 4.2 反转整数
- 4.3 排列硬币
- 4.4 完全平方数
- 4.5 消除游戏
- 第五章 双指针
- 第六章 字符串
- 6.1 整数转罗马数字
- 6.2 罗马数字转整数
- 6.3 反转字符串
- 6.4 压缩字符串
- 6.5 验证回文串
- 6.6 长按键入
- 6.7 字符串中的第一个唯一字符
- 第七章 二分查找
- 7.1 猜数字大小
- 第八章 分治算法
- 第九章 动态规划
- 9.1 爬楼梯
- 9.2 使用最小花费爬楼梯
- 9.3 打家劫舍
- 9.4 打家劫舍 II
- 9.5 第N个泰波那契数
- 第十章 回溯算法
- 第十一章 栈
- 11.1 棒球比赛
- 第十二章 堆
- 12.1 数组中的第K个最大元素
- 第十三章 贪心算法
- 第十四章 排序
- 14.1 冒泡排序
- 14.2 鸡尾酒排序
- 14.3 选择排序
- 14.4 插入排序
- 14.5 折半插入排序
- 14.6 希尔排序
- 14.7 快速排序
- 14.8 树形选择排序
- 14.9 堆排序
- 第十五章 位运算
- 15.1 只出现一次的数字
- 第十六章 思维题
- 16.1 TinyURL 的加密与解密
- 16.2 灯泡开关
- 16.3 字母板上的路径
- 第十七章 队列
- 17.1 扑克牌顺序