给你一个 升序排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。
1.直接判断法
当前元素与它下一个元素比较,因为注意题目说给定一个排序数组,是有序的,所以后面的肯定不小于前面的。只要不等于,那就是大于,就把这个值依次放到数组前面,因为前面的值不用再判断了,改变也没事。
2.使用双指针法则
这个算法的设计过程是这样的首先注意数组是有序的,那么重复的元素一定会相邻。要求删除重复元素,实际上就是将不重复的元素移到数组的左侧。考虑用 2 个指针,一个在前记作 p,一个在后记作 q,算法流程如下:
比较 p 和 q 位置的元素是否相等。
如果相等,q 后移 1 位
如果不相等,将 q 位置的元素复制到 p+1 位置上,p 后移一位,q 后移 1 位
重复上述过程,直到 q 等于数组长度。
返回 p + 1,即为新数组长度。
整个过程可以自己画图推导下。
3.将双指针法则优化一下,就是考虑一下数组
4.新的双指针法则,设计一个快指针一个慢指针
由于给定的数组nums 是有序的,因此对于任意i
当数组nums 的长度大于 0 时,数组中至少包含一个元素,在删除重复元素之后也至少剩下一个元素,因此 nums[0] 保持原状即可,从下标 1 开始删除重复元素。
定义两个指针fast 和slow 分别为快指针和慢指针,快指针表示遍历数组到达的下标位置,慢指针表示下一个不同元素要填入的下标位置,初始时两个指针都指向下标 1。
假设数组nums 的长度为 n。将快指针fast 依次遍历从 1 到 n−1 的每个位置,对于每个位置,如果 nums[fast] =nums[fast−1],说明nums[fast] 和之前的元素都不同,因此将]nums[fast] 的值复制到 nums[slow],然后将slow 的值加 1,即指向下一个位置。
遍历结束之后,从nums[0] 到nums[slow−1] 的每个元素都不相同且包含原数组中的每个不同的元素,因此新的长度即为slow,返回 slow 即可。
5.通用解法
为了让解法更具有一般性,我们将原问题的「最多保留 1 位」修改为「最多保留 k 位」。
对于此类问题,我们应该进行如下考虑:
由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留。
对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留。
「通用解法」是一种针对「数据有序,相同元素最多保留 k 位」问题更加本质的解法,该解法是从性质出发提炼的,利用了「数组有序 & 保留逻辑」两大主要性质。
基于上述解法我们还能做一点小剪枝:利用目标数组的最后一个元素必然与原数组的最后一个元素相同进行剪枝,从而确保当数组有超过 k 个最大值时,数组不会被完整扫描。
但需要注意这种「剪枝」同时会让我们单次循环的常数变大,所以仅作为简单拓展。
1.对于时间复杂度和空间复杂度的理解
2.++j和j++
当单独一个语句的时候没有区别,如果用在表达式中:
J++是先取J的值做计算,再自加;
++J是先自加,再取J的值做计算。
比如
int J = 2, K;
如果
K = J++;
那么先取J的值赋值给K,于是K值为2,J值再自加,J=3。
如果
K = ++J;
那么J先自加,得到J=3,然后再取J值计算,K值为3.
1.直接判断法
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()<2) return nums.size();
int j=0;
for(int i=1;i<nums.size();i++)
if(nums[j]!=nums[i]) nums[++j]=nums[i];
return ++j;
}
};
2.双指针法则
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int p = 0;
int q = 1;
while(q < nums.length){
if(nums[p] != nums[q]){
nums[p + 1] = nums[q];
p++;
}
q++;
}
return p + 1;
}
3.java实现
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int p = 0;
int q = 1;
while(q < nums.length){
if(nums[p] != nums[q]){
if(q - p > 1){
nums[p + 1] = nums[q];
}
p++;
}
q++;
}
return p + 1;
}
4.快速指针和慢指针
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
int fast = 1, slow = 1;
while (fast < n) {
if (nums[fast] != nums[fast - 1]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
};
5.java实现的
class Solution {
public int removeDuplicates(int[] nums) {
int t = 0;
for (int i = 0; i < nums.length; i ++ ) {
if (i == 0 || nums[i] != nums[i - 1]) nums[t ++ ] = nums[i];
}
return t;
}
}
6.通用解法
C++
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
return process(nums,1);
}
int process(vector<int>& nums,int k){
int idx = 0;
for(auto x : nums){
if(idx < k or nums[idx - k] != x){
nums[idx++] = x;
}
}
return idx;
}
};
通用解法java
class Solution {
public int removeDuplicates(int[] nums) {
return process(nums, 1);
}
int process(int[] nums, int k) {
int idx = 0;
for (int x : nums) {
if (idx < k || nums[idx - k] != x) nums[idx++] = x;
}
return idx;
}
}
剪枝操作
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
if (n <= 1) return n;
return process(nums, 1, nums[n - 1]);
}
int process(int[] nums, int k, int max) {
int idx = 0;
for (int x : nums) {
if (idx < k || nums[idx - k] != x) nums[idx++] = x;
if (idx - k >= 0 && nums[idx - k] == max) break;
}
return idx;
}
}
通用解法python
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
def process(nums, k):
idx = 0
for x in nums:
if idx < k or nums[idx-k] != x:
nums[idx] = x
idx += 1
return idx
return process(nums, 1)
2的复杂度
时间复杂度:O(n)。
空间复杂度:O(1)。
3时间复杂度:O(n)。
空间复杂度:O(1)。
4.时间复杂度:O(n),其中 n 是数组的长度。快指针和慢指针最多各移动 n 次。
空间复杂度:O(1)。只需要使用常数的额外空间。
6.通用解法
时间复杂度:O(n)
空间复杂度:O(1)