一、题目描述
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = [...]; // 输入数组 int[] expectedNums = [...]; // 长度正确的期望答案 int k = removeDuplicates(nums); // 调用 assert k == expectedNums.length; for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i]; }
如果所有断言都通过,那么您的题解将被 通过。
示例 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 <= nums.length <= 3 * 10^4
-10^4 <= nums[i] <= 10^4
nums
已按 非严格递增 排列
二、解题思路
1. 初始化两个指针:i
用于遍历数组 nums
,j
用于标记当前唯一的元素位置。
2. 遍历数组 nums
,对于每个元素:
- 如果
nums[i]
不等于nums[j-1]
(即当前元素与已记录的最后一个唯一元素不同),则将nums[i]
赋值给nums[j]
,并将j
加一,表示找到了一个新的唯一元素。 - 如果
nums[i]
等于nums[j-1]
,则跳过当前元素,不做任何操作。
3. 遍历结束后,j
的值即为数组中唯一元素的数量,也是新数组的长度。
三、具体代码
class Solution {
public int removeDuplicates(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int j = 0; // 标记当前唯一元素的位置
for (int i = 0; i < nums.length; i++) {
if (i == 0 || nums[i] != nums[j - 1]) {
nums[j] = nums[i]; // 遇到不同的元素,记录为唯一元素
j++; // 更新唯一元素的计数
}
}
return j; // 返回唯一元素的数量
}
}
四、时间复杂度和空间复杂度
这段代码的时间复杂度是 O(n),空间复杂度是 O(1)。
1. 时间复杂度
- 算法的核心是一个单层循环,它遍历了整个数组
nums
。 - 在每次迭代中,我们执行常数时间的操作,包括比较和可能的赋值。
- 因此,整个算法的时间复杂度是 O(n),其中 n 是数组
nums
的长度。
2. 空间复杂度
- 我们没有使用额外的空间来存储数据,所有的操作都是在原数组上进行的。
- 我们使用了一个额外的变量
j
来跟踪唯一元素的位置,但这个变量的空间需求是常数的,不依赖于输入数组的大小。 - 因此,算法的空间复杂度是 O(1),即常数空间复杂度。
五、总结知识点
-
数组处理:代码处理一个整数数组
nums
,这是数据结构中的一个基本概念。 -
边界检查:在处理数组之前,代码首先检查数组是否为
null
或者长度为0
,这是良好的编程习惯,可以避免空指针异常或其他潜在的错误。 -
原地修改:代码在原数组上进行修改,而不是创建一个新的数组来存储唯一元素。这种方法节省了空间,但要求我们对数组的某些部分进行重新排列。
-
双指针技术:使用两个指针
i
和j
。i
用于遍历数组,而j
用于标记当前唯一元素的位置。这是一种常见的优化技术,用于在遍历过程中跟踪特定状态。 -
条件赋值:在循环内部,使用条件语句
if (i == 0 || nums[i] != nums[j - 1])
来决定是否将当前元素赋值给nums[j]
。这确保了只有当当前元素与前一个唯一元素不同时,它才会被记录。 -
循环控制:循环
for (int i = 0; i < nums.length; i++)
控制了遍历数组的过程,这是循环结构的基本应用。 -
数组索引:在赋值操作
nums[j] = nums[i];
中,使用了数组索引来访问和修改数组元素。 -
递增计数器:变量
j
作为计数器,用于记录新数组的长度,这是计数器在循环中的典型应用。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。