目录
一、移除元素
(一)题目


(二)解题思路
关于顺序表算法题的解题思路:下策遍历 、中策新数组 、上策原数组操作。
1、下策:纯遍历
① 遍历数组,当发现目标值 val 时,用临时变量保存该值。
② 将 val 后面的所有元素依次向前移动一位,覆盖掉当前的 val。
③ 把临时变量中保存的 val 放到数组的最后位置。
④ 重复上述过程,直到遍历完数组,最终数组前面部分就是移除 val 后的有效元素,后面部分是被移到末尾的 val。
这样虽然可以完成目标,但是时间复杂度为O(n²),效率不高。
cpp
int removeElement(int* nums, int numsSize, int val)
{
int size = numsSize; // 记录有效元素长度
// 外层循环遍历所有元素,将i++移到for循环的更新部分
for (int i = 0; i < size; i++)
{
if (nums[i] == val)
{
// 找到目标值,内层循环将后续元素前移一位
for (int j = i; j < size - 1; j++)
nums[j] = nums[j + 1]; // 逐个覆盖
size--; // 有效长度减1
i--; // 关键:当前位置元素被替换,需要重新检查,因此i回退1
}
}
return size;
}
2、中策:新数组
① 新建数组:创建一个临时数组,用于存储原数组中不等于目标值 val 的元素。
② 遍历原数组:逐个检查原数组元素,若元素不是 val,就将其加入临时数组。
③ 覆盖原数组:将临时数组中的元素按顺序复制到原数组的起始位置,完成后原数组前半部分就是过滤掉 val 后的结果。
这样子时间复杂度就被优化到了O(n),但是空间复杂度是O(n),所以还并不是最佳的。
cpp
int removeElement(int nums[], int numsSize, int val)
{
// 处理数组为空的特殊情况(numsSize = 0)
if (numsSize == 0)
return 0;
// 临时数组,此时numsSize至少为1,避免长度为0
int tempArr[numsSize];
int j = 0; // 临时数组的下标
// 遍历原数组,收集非 val 元素
for (int i = 0; i < numsSize; i++)
{
if (nums[i] != val)
tempArr[j++] = nums[i];
}
// 将临时数组元素覆盖到原数组前 j 个位置
for (int i = 0; i < j; i++)
nums[i] = tempArr[i];
// 返回新的有效长度
return j;
}
3、上策:在原数组操作
(1)解题思路:双指针法
**(2)核心思想:**用两个指针(src、dst)遍历数组,src探路找 "非val值",dst占位存 "非val值",实现 "原地移除"(无需额外数组)。
(3)指针分工
**① src(源指针):**从左到右遍历数组,负责 "找" 非val值。
**② dst(目标指针):**从左到右移动,负责 "存"src找到的非val值。
(4)步骤

① 初始化src = 0,dst = 0
② src遍历数组:
若nums[src] == val:src直接后移(跳过val)。
若nums[src] != val:将nums[src]赋值给nums[dst],dst后移,src后移。
③遍历结束,dst的值即为有效元素个数k。
(5)代码实现
cpp
int removeElement(int* nums, int numsSize, int val)
{
int dst = 0;
int src = 0;
for (src = 0; src < numsSize; src++)
{
//src的值为val,src++
//src的值部位val,赋值再++
if (nums[src] != val)
{
nums[dst] = nums[src];
dst++;
}
}
return dst; // dst即为有效元素个数k
}
二、删除有序数组中的重复项
(一)题目


(二)解题思路
1、核心思想
利用 "有序数组重复项必相邻" 的特性,用 dst 标记 "非重复元素的末尾位置",src遍历找 "下一个非重复元素"。
2、指针分工
**(1)dst(目标指针):**初始为 0,指向当前最后一个非重复元素。
**(2)src(源指针):**初始为 1,从第二个元素开始遍历,找与dst指向元素不同的值。
3、步骤

**(1)**若数组为空,直接返回 0。
**(2)**初始化dst=0,src=1。
**(3)**src遍历数组
①若nums[src] == nums[dst]:src后移(跳过重复项)。
②若 nums[src] != nums[dst] ,说明找到新的非重复元素;dst 后移一位,准备存储新元素;若 dst和 src 不相邻 (避免无效赋值),则将 nums[src] 赋给 nums[dst];src 始终后移,继续遍历。
③ 遍历结束,返回dst+1(dst是下标,长度需 + 1)。
4、代码实现
cpp
int removeDuplicates(int* nums, int numsSize)
{
if (numsSize == 0)
return 0; // 处理空数组
int dst = 0;
int src = dst + 1;
while(src < numsSize)
{
if (nums[src] != nums[dst]) {
dst++; // 目标指针后移
// 只有当两个指针不相邻时才需要赋值(避免自己赋给自己)
if (dst != src)
nums[dst] = nums[src];
}
src++;
}
return dst + 1; // 返回非重复元素个数
}
(1)时间复杂度:O (n)(仅遍历数组 1 次)。
(2)空间复杂度:O (1)(无额外申请空间,原地操作)。
三、合并两个有序数组
(一)题目


(二)解题思路(三指针法)
1、核心思想
若从前往后遍历,nums1的有效数据会被覆盖(如nums1[0]可能被nums2[0]覆盖);所以要从后往前遍历,利用nums1的空位置(后n位),避免覆盖。

2、指针分工
**(1)l1:**指向nums1有效数据的末尾(初始为m-1)。
**(2)l2:**指向nums2的末尾(初始为n-1)。
**(3)l3:**指向nums1合并后的末尾(初始为m+n-1)。
3、步骤
**(1)**比较nums1[l1]和nums2[l2],将较大值放入nums1[l3]。
**(2)**对应指针左移(如放nums1[l1]则l1--,放nums2[l2]则l2--),l3始终左移。
**(3)**若 nums2 先遍历完 (l2 < 0),结束;若nums1先遍历完 (l1 < 0),将 nums2 剩余元素直接拷贝到 nums1 前端。
4、代码实现
cpp
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
// 初始化三个指针
int l1 = m - 1; // nums1有效数据末尾
int l2 = n - 1; // nums2末尾
int l3 = m + n - 1;// nums1合并后末尾
// 第一步:合并两个数组的有效数据
// 只要有一个不满足条件,就不可以进入循环,有一个条件为假就不可以
// 都为真才可以进入循环
// l3--;是先用后减,等价于省略了l3--
while (l1 >= 0 && l2 >= 0) {
if (nums1[l1] > nums2[l2])
nums1[l3--] = nums1[l1--];
else
nums1[l3--] = nums2[l2--];
}
// 第二步:处理nums2剩余元素(若nums1先遍历完)
// l1越界导致上面循环停止的话,那么也就完成了
// 如果是l2越界导致上面循环停止的话,那么nums2的剩余内容到nums1中去
while (l2 >= 0) {
nums1[l3--] = nums2[l2--];
}
}