【刷题之路】LeetCode 26. 删除有序数组中的重复项

一、题目描述

原题连接: 26. 删除有序数组中的重复项

题目描述:
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次
,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,
如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:
输入: nums = [1,1,2]
输出: 2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:
输入: nums = [0,0,1,1,1,2,2,3,3,4]
输出: 5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

二、解题

1、方法1——暴力法

1.1、思路分析

既然题目已经说是升序序列,那就说明相同元素一定是紧挨在一起的,
那我们可以直接遍历数组,当发现nums[i] = nums[i + 1]时,就将i后面的元素全都往前移一位,并把数组长度减1:
在这里插入图片描述

因为考虑到nums[i]会有两个或两个以上个元素与它相等,所以我们完成一次整体移动之后,下一次判断还是要在i的位置进行(也就是i不进行++操作)。直到nums[i] != nums[i + 1]
才能让i++。

1.2、代码实现

有了以上分析,那我们写起代码来也就水到渠成了:

int removeDuplicates1(int* nums, int numsSize) {
	assert(nums);
	int i = 0;
	int j = 0;
	for (i = 0; i < numsSize - 1; i++) {
		if (nums[i] == nums[i + 1]) {
			for (j = i; j < numsSize - 1; j++) {
				nums[j] = nums[j + 1];
			}
			numsSize--; // 这里让i--上面让i++其实i还是保持不变。
			i--;
		}
	}
	return numsSize;
}

时间复杂度:O(n^2),n为数组元素个数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

2、方法2——标记值标记法

2.1、思路分析

我们可以设置一个标记值val,目的是标记数组中已经出现过的数字,初始时我们让val等于nums[0],并用一个变量n来记录数组中不相同元素的个数,n的初始值为1:
在这里插入图片描述
然后遍历数组,当nums[i] == val时,说明是重复出现的val,不做任何操作:
在这里插入图片描述
当nums[i] != val时,就将nums[i]写到nums[1],并将val的值更新为nums[i],且n++:
在这里插入图片描述
当再次发现一个元素不等于val时,就将这个元素写到nums[2],并将val的值更新为这个元素的值,且n++。
以此类推,当遍历完数组时我们就完成了所有删除。
这样我们最后直接return n即可。
其实这个思路,也就是我们平时所说的“双指针”,只不过我这里想换一种说法给大家分析。

2.2、代码实现

有了以上分析,那我们写起代码来也就水到渠成了:

int removeDuplicates2(int* nums, int numsSize) {
	assert(nums);
	int val = nums[0];
	int n = 1;
	int i = 0;
	for (i = 0; i < numsSize; i++) {
		if (nums[i] != val) {
			nums[n] = nums[i];
			val = nums[i];
			n++;
		}
	}
	return n;
}

时间复杂度:O(n),n为数组元素个数,我们只需要遍历一遍数组即可。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

3、方法3——找相同子序列的最后一个

3.1、思路分析

想一下有什么方法能在一层循环中直接找出数组中不同元素的个数而又不需要借助标记值呢?
其实我们可以通过循环找到一个相同元素的序列中最后被遍历到的那个元素:
在这里插入图片描述
即当nums[i] != nums[i + 1]
时,nums[i]就是那最后一个元素。把他们依次写入数组的前半部分,最后再返回不同元素的个数即可。
但是对于数组的最后两个元素,则需要进行特殊处理,因为对于最后一个元素,就无法判断nums[i] != nums[i + 1],若是nums[i] != nums[i + 1] 则我们只是把倒数第二个元素写进了前面,漏掉了最后一个,而若是nums[i] == nums[i + 1],那我们就会少了一个不同的值。
所以我们应该这样,不管最后一个和倒数第二个元素是否相等,我们都要把最后一个元素写到数组的前面。

3.2、代码实现

有了以上分析,那我们写起代码来也就水到渠成了:

int removeDuplicates3(int* nums, int numsSize) {
	assert(nums);
	if (1 == numsSize) {
		return 1;
	}
	int n = 0;
	int i = 0;
	for (i = 0; i < numsSize - 1; i++) {
		if (nums[i] != nums[i + 1]) {
			nums[n] = nums[i];
			n++;
		}
		if (i == numsSize - 2) {
			nums[n] = nums[i + 1];
			n++;
		}
	}
	return n;
}

时间复杂度:O(n),n为数组元素个数。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林先生-1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值