力扣 顺序表 OJ题型训练
27. 移除元素
1. 题目介绍
- 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
- 要求 : 空间复杂度为 O(1)
2. 题目解析
- 给出指定数组nums ,与指定值 val。 将数组nums中的所有值为val的元素删除。 最后返回该数组剩余元素的个数。
- 原地移除, 代表不开辟新空间, 空间复杂度为常数阶。
3. 题解
双指针的应用
- 此处双指针并非真的是指针类型的变量, 只是两个起到指针作用的int类型变量
- 同向双指针: 两个指针均从数组头部向数组尾部进行移动
- 反向双指针 : 两个指针一个从数组头部向数组尾部移动, 另一个指针从数组尾部向数组头部进行移动
1> 同向双指针
思路详解:
采取两个同向移动的指针用来遍历数组nums中的元素, 两个指针同时同向移动, 每遍历到一个非val元素指针一都会将其指向的元素的值 赋给指针二所指向的元素
区别在于:
- 指针一在边路数组nums中的元素时,并不会停下
- 指针二在遍历数组nums中的元素时, 遭遇到值为val的元素时, 就会停止向后移动
该循环结束的条件为, 指针一完全遍历数组nums, 此时指针一指向数组中最后一个元素的下一位置。
思路图解:
代码实现:
class Solution {
public:
int removeElement(vector<int>& nums, int val)
{
int n = nums.size(); // 数组中元素个数
int second = 0; // 指针二
for (int first = 0; first < n; first++) //first 指针一
{ // 指针一持续移动
if (nums[first] != val) // 当指针一 指向的元素不为val时
{
nums[second] = nums[first];// 将这个元素赋值给指针二
second++; // 指针二进行++
}
} // 当指针一指向的元素为val时, 指针一继续移动,
return second;// 指针二停止移动, 且此时不再进行赋值操作。
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/remove-element/solutions/730203/yi-chu-yuan-su-by-leetcode-solution-svxi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度 O(n)
空间复杂度 O(1)
2> 反向双指针
思路详解:
采取左右两个指针向中间移动, 当左右两个指针指向同一个元素时, 遍历一遍数组中的所有元素。
我们先让左指针进行移动, 直到左指针遇到 val。 此时让左指针停下,
然后再让右指针进行移动,直到右指针遇到非 val 。 此时让右指针也停下。
然后我们交换两个指针指向的元素。
当我们交换完成后, 此时左指针指向非val, 右指针指向val。 它们又可以继续移动了, 重复上述步骤。
由于是左指针先进行移动, 那么左指针会先移动到 右指针指向的元素,此时完全遍历数组中元素。 此时返回 左指针 的大小就是数组新元素的个数
class Solution {
public:
int removeElement(vector<int>& nums, int val)
{
int n = 0;
int m = nums.size(); // 注意 m = 元素个数, 用于索引需要 -1
int temp = 0;
while (n < m) // n<m,m=nums.size() 适用于特殊情况,
{ // 数组中只有一个元素, 且该元素不是val
while (nums[n] != val)
{
n++;
if(n>=m) // 防止 nums[n] 越界, n++后恰好越界
{
break; // 直接跳出当前while循环,防止下次判断
} // nums[n] != val ,有可能越界
}
while (nums[m-1] == val)
{
m--;
if(m == 0) // 同理防止越界
break;
}
if(n>=m) // n ,m-1指向同一元素时,n<M
break; // 还会在进行一次while循环,
temp = nums[n]; // 而下次while循环有可能会数组越界
nums[n] = nums[m-1]; // 因此需要跳出最外层while循环
nums[m-1] = temp;
}
return n; // n 的值代表 数组中新元素的个数
}
};
作者: Joker不是Joker
时间复杂度 O(n)
空间复杂度 O(1)
反向指针相比于同向指针, 它避免了繁琐的赋值操作,且数组中有几个val它才交换几次(甚至少交换一次)
优点在于 交换次数少,
缺点在于 需要进行多次判断防止数组越界
26. 删除有序数组中的重复项
1 . 题目介绍
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
2. 题目提示
- 考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
- 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
- 返回 k 。
3 . 题目解析
该数组中为非严格递增数列, 可以将其认为一个类似递增的数列, 区别在于该数列中存在相同元素。 删除重复元素指的是 ,重复只保留一个。
最后所得数组, 该数组中元素的相对顺序不能改变。
也就是说, 最后所得结果为一个递增序列的数组。
返回删除重复元素后数组中剩余元素的个数
4 . 题解
双指针的应用:
类似于上题, 依旧是双指针的应用
方式一:
class Solution {
public:
int removeDuplicates(vector<int>& nums)
{
int src=0;
int dst=0;
while(src<nums.size())
{
if(src ==0) // 当dst =0时, nums[dst-1]会越界,因此在dst = src = 0时, 均对其++
{
src++; // dst = src = 0 必须存在的意义是: 数组中仅有一个元素, 此时++之后直接退出while循环
dst++;
}
else
{
if(nums[src] != nums[dst]) // src 与dst指向元素不同, 就将src指向的元素赋给dst
{
nums[dst] = nums[src];
}
if(nums[dst] != nums[dst -1]) // 判断dst 指向的元素与其前一个是否相同, 不同则++ ,相同dst停止移动, 等待下次循环 当前所指向元素被覆盖
{
dst++;
}
src++;
}
}
return dst;
}
};
作者 : Joker不是Joker
时间复杂度: O(n)
空间复杂度: O(1)
方式二:
双指针代码优化
class Solution {
public:
int removeDuplicates(vector<int>& nums)
{
int src=0;
int dst=0;
while(src<nums.size())
{
if(nums[src] !=nums[dst]) // 判断条件变为 src 与dst指向元素是否相同, 相同src移动, dst停止。 指向元素不同时,src赋值给dst-1之后二者同时移动
{
if((src -dst)>1) // 避免src自我赋值
{
nums[dst+1] = nums[src];
}
dst++;
}
src++;
}
return dst+1;
}
};
作者: Joker不是Joker
时间复杂度: O(n) //时间上超越100%用户
空间复杂度: O(1)
88 . 合并两个有序数组
1. 题目介绍
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
2. 题目提示
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
3. 题目解析
该题, 是将两个非严格递增序列的数组合并为一个非严格递增序列的数组。 无时间或空间复杂度的要求。
4. 题解
方式一:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
{
int src=m-1, dst=n-1;
for(int i=m+n-1; i>=0; i--)
{
if(src == -1)
{
nums1[i] = nums2[dst--];
}
else if(dst == -1)
{
nums1[i] = nums1[src--];
}
else if(nums1[src] >= nums2[dst])
{
nums1[i] = nums1[src--];
}
else
{
nums1[i] = nums2[dst--];
}
}
}
};
// 此方式过于臃肿
作者 : Joker不是Joker
时间复杂度: O(m+n)
空间复杂度: O(1)
方式一优化:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
{
int src=m-1, dst=n-1;
int count= m+n-1;
while(src>=0 || dst>=0) // 将for循环用while循环替代,循环结束条件并非遍历(m+n)个元素, 而是当某一个数组被遍历完, 另一个数组对nums1进行赋值或是不赋值
{
if(src == -1)
{
nums1[count--] = nums2[dst--];
}
else if(dst == -1)
{
src--;
}
else if(nums1[src] >= nums2[dst])
{
nums1[count--] = nums1[src--];
}
else
{
nums1[count--] = nums2[dst--];
}
}
}
};
作者 : Joker不是Joker
时间复杂度: O(m+n) // 时间上超越100%用户
空间复杂度: O(1)