【C/C++练习】经典的快慢指针问题---移除元素

简介: 【C/C++练习】经典的快慢指针问题---移除元素

77eaa8bfa8524a63a69214c39bd6d414.gif

📖题目描述

0f05589b482e43b8a8ad293f209788fa.png


题目出处:移除元素

🔖示例

246d6c31f0584e5fb5966a821f9b4aa2.png

📖题解

 对于本题我将按照由易到难的顺序为大家分享三种解题思路,并逐一分析它们的优劣,以及注意事项。

🔖思路一:暴力求解

 我想暴力求解应该是第一次接触到此题的小伙伴们最先想出来的办法吧。这道题目暴力求解就是去遍历数组,当遇到数组元素等于val的时候,将后面的所有元素往前挪动一位,把val覆盖掉以实现移除的效果。具体过程如下动图所演示:

ccd62b880abc4fb39eb909ee4cc43456.gif

代码实现:

int removeElement(int* nums, int numsSize, int val)
{
    int i = 0;
    int len = numsSize;
    while(i < len)
    //循环控制变量用len,因为如果有重复,就会往前覆盖
    {
        if (nums[i] == val)
        {
            int j = 0;
            for (j = i; j < len; j++)
            //把后面的往前覆盖
            {
                if (j != numsSize - 1)
                {
                    nums[j] = nums[j + 1];
                }
            }
            len--;
            //找到一个之后跳出循环,继续从下标为0的数字开始检查
            }
        else
        {
            i++;
        }
    }
    return len;
}

需要注意:i是用来遍历整个数组的,当nums[i] == val的时候停下来,把i后面的所有数据往前挪动一位,且数组的有效长度len减一。挪动完后i的值不变,因为此时i位置上的元素是它后一位经过挪动覆盖过来的,我们并不知道他是否等于val,因此需要继续判断num[i]是否等于val,如果不等于再让i++继续去遍历后面的元素,直到i等于数组的有效长度,此时说明已经遍历结束。

复杂度分析:

 时间复杂度要讨论最坏的情况,对于本题最坏情况就是数组中所有的元素都等于val,假设数组一共有N NN个元素,那么将第一个元素移除需要把后面的N − 1 N-1N−1个元素全部往前挪动一位,也就是挪动N − 1 N-1N−1次,由于数组元素全是val所以经过挪动后第一个元素任然是val,但此时数组的有效元素个数已经变成了N − 1 N-1N−1,所以此时需要挪动N − 2 N-2N−2…以此类推总的挪动次数就是一个等差数列求和,因此时间复杂度就是O ( N 2 ) O(N^2)O(N 2 )

🔖思路二:空间换时间

 如果对空间复杂度没有要求的情况下,我们可以创建一个新数组,然后去遍历原数组,如果num[i]不等于val就把他拷贝到新数组,如果等于那就跳过并且有效数据个数减一,直到遍历结束,然后把新开数组的内容拷贝回原数组。具体过程如下动图所示:


89664d15c0204f098aedf62c13bef11e.gif

代码实现:

int removeElement(int* nums, int numsSize, int val)
{
    int len = 0;
    int* tmp = (int*)malloc(sizeof(int)*numsSize);
    for(int i = 0; i < numsSize; i++)
    {
        if(nums[i] != val)
        {
            tmp[len++] = nums[i]; 
        }
    }
    memcpy(nums, tmp, sizeof(int)*len);
    return len;
}

复杂度分析:

 时间复杂度方面,这种方案只遍历了一遍数组,因此它的时间复杂度是O ( N ) O(N)O(N),在空间复杂度方面,由于开辟了一个新数组,所以空间复杂度是O ( N ) O(N)O(N)。

🔖思路三:双指针

这种思路的具体过程是:同时维护两个指针,分别记作pri和pos,让他俩从第一个元素开始同时往后走,当pos指向的元素不等于val的时候,把其赋值给pri所指向的位置,然后两个指针继续往后走,如果pos指向的元素等于val,那么pri停下来,pos跳过这个元素继续往后走,直到pos指向的元素不等于val,把它赋值给pri指向的位置,然后两个指针继续同时往后走,直到pos指针来到数组的结尾。具体过程如下动图所示:


5d45ad08b87a419eaacd024f7572c0c9.gif

代码实现:

int removeElement(int* nums, int numsSize, int val)
{
    int pri = 0, pos = 0;
    while(pos < numsSize)
    {
        if(nums[pos] != val)
        {
            nums[pri] = nums[pos];
            pri++;
        }
        pos++;
    }
    return pri;
}

复杂度分析:

 在时间复杂度方面,这种方法只遍历了一遍数组,所以时间复杂度是O ( N ) O(N)O(N),在空间复杂度方面,由于没有额外申请空间所以空间复杂度是O ( 1 ) O(1)O(1)。对于这道题思路三无疑是最好的方法,这种快慢指针的思想大家要多多去体会。

📖题目描述

df548223fac24e45b19593f0edfdf881.png

191501b784e646eea420c4c8cb527fea.png

题目出处:删除有序数组中的重复项

📖题解


有了上面的分析基础,我们应该很容易猜到这道题也可以使用双指针的方法来做,但是这个题的双指针与上面的又有所不同。这道题要让pri指针从第一个元素开始,pos指针从第二个元素开始,因为相同的元素只需要保存一个,如果pos指向的元素与pri指向的元素相等就让pos往后走而pri不动,直到pos指向的元素不等于pri指向的元素,此时先让pri往后走一步,然后把pos指向的元素赋值给pri指向的元素,具体过程如下动图所示:


a2333218224349f68c80005f04551f37.gif

代码实现:

int removeDuplicates(int* nums, int numsSize)
{
    int pri = 0;
    int pos = pri + 1;
    while(pos < numsSize)
    {
        if(nums[pos] != nums[pri])
        {
            pri++;
            nums[pri] = nums[pos];
        }
        pos++;
    }
    return ++pri;
}

 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!

目录
相关文章
|
17天前
|
存储 安全 C++
C++中的引用和指针:区别与应用
引用和指针在C++中都有其独特的优势和应用场景。引用更适合简洁、安全的代码,而指针提供了更大的灵活性和动态内存管理的能力。在实际编程中,根据需求选择适当的类型,能够编写出高效、可维护的代码。理解并正确使用这两种类型,是掌握C++编程的关键一步。
21 1
|
17天前
|
数据采集 存储 编译器
this指针如何使C++成员指针可调用
本文介绍了C++中的this指针,它是一个隐藏的指针,用于在成员函数中访问对象实例的成员。文章通过代码示例阐述了this指针的工作原理,以及如何使用指向成员变量和成员函数的指针。此外,还提供了一个多线程爬虫示例,展示this指针如何使成员指针在对象实例上调用,同时利用代理IP和多线程提升爬取效率。
this指针如何使C++成员指针可调用
|
3天前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
32 5
|
2天前
|
C++ 容器
【编程技巧】 C++11智能指针
C++11引入了智能指针以自动管理内存,防止内存泄漏和悬挂指针: - `shared_ptr`:引用计数,多所有权,适用于多个对象共享资源。 - `unique_ptr`:独占所有权,更轻量级,适用于单一对象所有者。 - `weak_ptr`:弱引用,不增加引用计数,解决`shared_ptr`循环引用问题。 ## shared_ptr - 支持引用计数,所有者共同负责资源释放。 - 创建方式:空指针、new操作、拷贝构造/移动构造,以及自定义删除器。 - 提供`operator*`和`operator-&gt;`,以及`reset`、`swap`等方法。 ## unique_ptr
|
3天前
|
C++ 容器
C++之评委打分案例(vector与deque容器练习)
C++之评委打分案例(vector与deque容器练习)
8 1
|
5天前
|
存储 Java C#
C++语言模板类对原生指针的封装与模拟
C++|智能指针的智能性和指针性:模板类对原生指针的封装与模拟
|
3天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
5 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
5天前
|
设计模式 C++ 开发者
C++一分钟之-智能指针:unique_ptr与shared_ptr
【6月更文挑战第24天】C++智能指针`unique_ptr`和`shared_ptr`管理内存,防止泄漏。`unique_ptr`独占资源,离开作用域自动释放;`shared_ptr`通过引用计数共享所有权,最后一个副本销毁时释放资源。常见问题包括`unique_ptr`复制、`shared_ptr`循环引用和裸指针转换。避免这些问题需使用移动语义、`weak_ptr`和明智转换裸指针。示例展示了如何使用它们管理资源。正确使用能提升代码安全性和效率。
14 2
|
10天前
|
存储 算法 安全
C++一分钟之-数组与指针基础
【6月更文挑战第19天】在C++中,数组和指针是核心概念,数组是连续内存存储相同类型的数据,而指针是存储内存地址的变量。数组名等同于指向其首元素的常量指针。常见问题包括数组越界、尝试改变固定大小数组、不正确的指针算术以及忘记释放动态内存。使用动态分配和智能指针可避免这些问题。示例代码展示了安全访问和管理内存的方法,强调了实践的重要性。
26 3
|
15天前
|
C++
C++小练习:猜数游戏
C++小练习:猜数游戏