根据剑指offer数组中的重复元素,我在Leetcode上面找了几道类似的题进行练习。
1.删除排序数组中的重复项I(Leetcode.26)
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,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。
你不需要考虑数组中超出新长度后面的元素。
根据题目的意思,首先数组是排序的,其次是要原地删除所有的重复元素。
1.1直接使用vector的内置函数
直接定义好vector的迭代器,然后比较相邻元素,如果相同,则删除该元素,注意使用erase删除之后,迭代器会自动指向下一个元素。否则,就将迭代器++.
public:
int removeDuplicates(vector<int>& nums) {
vector<int>::iterator it;
if(nums.size()==0)
return nums.size();
for(it=nums.begin();it!=nums.end()-1;)
{//边界问题
if(*it==*(it+1))
nums.erase(it);
else
it++;
}
return nums.size();
}
1.2 使用快慢指针
已知数组元素是有序的,我们可以设定两个指针 j , i j,i j,i, j j j指向第0个元素, i i i指向第一个元素,如果 j j j和 i i i所指向的元素相等,那么就 i i i++,判断下一个元素是否相等。如果如果 j j j和 i i i所指向的元素不相等,那么就将 j j j++,并且使得 j j j当前指向的元素更新为 i i i所指向的元素。比如 1 , 1 , 2 , 3 {1,1,2,3} 1,1,2,3, j = 0 , i = 1 j=0,i=1 j=0,i=1起初,因为 n u m [ j ] = = n u m [ i ] num[j]==num[i] num[j]==num[i],因此我们将 i i i++,而 j j j依然不变。更新后, i = 2 i=2 i=2,num[2]!=num[0],将 j j j++,并且将用2放在当前位置,即num[1]=2.这里的原地删除的意思是我们只需要保证前len个元素是符合要求的,而len后面的不需要关心。因为输出是这样的:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
public:
int removeDuplicates(vector<int>& nums) {
int j=0;
if(nums.size()<2)
return nums.size();
for(int i=1;i<nums.size();i++)
{
if(nums[j]!=nums[i])
nums[++j]=nums[i];
}
return ++j;
}
2.删除排序数组中的重复项II(Leetcode.80)
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 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 。
你不需要考虑数组中超出新长度后面的元素。
相比于第一道,这道题的要求是保留2个重复数字。也就是重复的定义改变了,2个以上才是重复。依然是排序数组,所以依旧可以使用快慢指针,只是需要额外增加一个计数器表示元素的数量。在判断 j j j和 i i i所指向的元素相等时,需要额外考虑数量是否达到2个,如果没有达到两个,就更新 j j j写入数组。在 j j j和 i i i所指向的元素不相等时,就表示有新元素了,更新 j j j并且将元素写入数组,更新计数器重新计数。
public:
int removeDuplicates(vector<int>& nums) {
int j=0;
int k=1;
if(nums.size()<=2)
return nums.size();
for(int i=1;i<nums.size();i++)
{
if(nums[j]==nums[i]&&k<2)
{
nums[++j]=nums[i];
++k;
}
if(nums[j]!=nums[i])
{
nums[++j]=nums[i];
k=1;
}
}
return ++j;
}
3.存在重复元素I(Leetcode.217)
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
这道题,数组不是有序的,如果想要使用快慢指针来判断,需要先排序。
public:
bool containsDuplicate(vector<int>& nums) {
sort(nums.begin(),nums.end());
int n=nums.size();
if(n<2)
return false;
int j=0;
for(int i=1;i<nums.size();i++)
{
if(nums[j]!=nums[i])
nums[++j]=nums[i];
}
if(n>(++j))
return true;
else
return false;
}
4.存在重复元素II(Leetcode.219)
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。
示例 1:
输入: nums = [1,2,3,1], k = 3
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1
输出: true
示例 3:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false
相比于上一道题,这一道题增加了一个约束条件,相同元素下标之间的距离。这就要求我们不可以随便排序,因为排序之后下标会发生变化。需要记录下标又需要比较元素,我们就想到采用map,建立一个map,使用数组元素作为键值,使用下标作为value。遍历数组,我们查询每一个元素是否在map中,如果在,那么二者下标的距离是否满足约束,如果满足返回true,如果不满足则更新下标为较大的下标。如果不在,则加入map.这里查找的时候使用STL中map查找函数find,如果找不到,则返回指向end的迭代器。查找的时间代价为 O ( l o g n ) O(logn) O(logn)
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n=nums.size();
map<int,int>hash_n;
for(int i=0;i<n;i++)
{
if(hash_n.find(nums[i])!=hash_n.end())//exist
{
if(i-(hash_n.find(nums[i])->second)<=k)
return true;
else
(hash_n.find(nums[i]))->second=i;
}
else
{
hash_n[nums[i]]=i;
}
}
return false;
}
5.数组中的重复数据(Leetcode.442)
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]
这道题是无序的,数组元素有范围。所以需要考虑如何标记已经遍历过的数组元素。
因此有下标范围,所以我们可以将数组视为一个哈希表。将遍历过的元素标记为其相反数。
public:
vector<int> findDuplicates(vector<int>& nums) {
//标记索引 使用a[i]作为索引
vector<int>result;
for(int i=0;i<nums.size();i++)
{
int nums_abs=abs(nums[i]);
if(nums[nums_abs-1]>0)
nums[nums_abs-1]*=-1;//标记为负数
else
result.push_back(abs(nums[i]));
}
return result;
}
总结
1.数组是否有序:数组有序可以考虑快慢指针,数组无序又不在意下标可以排序再使用快慢指针。
2.数组元素是否有范围:数组元素有范围且可以用作下标,可以使用数组做hash表;数组元素没有范围可以使用map做hash表。