leecode移除数组中元素相关问题(快慢指针法)C++

这篇博客主要介绍了如何使用C++通过快慢指针法解决LeetCode中关于移除数组元素的问题,包括27. 移除元素、26. 删除有序数组中的重复项、283. 移动零等。讨论了不同题目中对于原地修改数组的要求和解决方案,以及不同情况下的时间复杂度和空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

移除数组中元素相关问题

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

解答:
思想:使用快慢指针法,返回慢指针的最后的索引即可。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow=0;
        for(int fast=0;fast<nums.size();fast++){
            if(val!=nums[fast]){
                nums[slow++]=nums[fast];
            }
        }
        return slow;
    }
};

该方法时间复杂度:
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
也可以用暴力解法,两个for嵌套循环。
那样的时间复杂度就是:
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 :

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 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 。不需要考虑数组中超出新长度后面的元素。

提示:
0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按 升序 排列

解答:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slow=0;
        for(int fast=1;fast<nums.size();fast++){
            if(nums[fast]!=nums[slow]){
                nums[++slow]=nums[fast];   //注意此处要先++,先后移满指针
            }       
        }
        return slow+1;
    }
};

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

输入: nums = [0]
输出: [0]

提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1

解答:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow=0;
        for(int fast=0;fast<nums.size();fast++){
            if(nums[fast]!=0){
                nums[slow++]=nums[fast];
            }
            
        }
        for(int i=slow;i<nums.size();i++){
            nums[i]=0;
        }
    }
};

844. 比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。

输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""。

输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"1 <= s.length, t.length <= 200
s 和 t 只含有小写字母以及字符 '#'

解答;

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        string s1,t1;
        s1=deal(s);
        t1=deal(t);
        
        return s1==t1; 
    }
    string deal(string s){
        if(s==""){return s;}
        int slow=0;
        for(int fast=0;fast<s.size();fast++){
            if(s[fast]!='#'){
                s[slow++]=s[fast];
            }
            else{
                slow--;
                if(slow<0){slow=0;}
            }        
        }
        return s.substr(0,slow);
    }
};

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序

解答:
思想:1、新建一个数组用来存储结果数组。2、双指针,一个从头开始,另一个从尾巴开始,取平方值大的存储起来。3、然后指针前移或后移。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> tmp(nums.size(),0);
        int front=0;
        int final=nums.size()-1;
        int k=nums.size()-1;
        while(front<=final){
            if((nums[final]+nums[front])<0){
                tmp[k]=nums[front]*nums[front];
                front++;
                k--;
            }
            else{
                tmp[k]=nums[final]*nums[final];
                final--;
                k--;
            }
        }
        return tmp;
    }
};

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

输入:target = 4, nums = [1,4,4]
输出:1

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

解法一:暴力法
思想:两个for嵌套,一个一个筛查。
代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result=nums.size()+1;
        int sum=0;
        int sub=0;
        for(int i=0;i<nums.size();i++){
            sum=0;
            for(int j=i;j<nums.size();j++){
                sum+=nums[j];
                if(sum>=target){
                    sub=j-i+1;
                    if(sub<result)
                        result=sub;;
                        break;
                    }
            }
        }
        if(result<=nums.size()){return result;}
        else{return 0;}

    }
};

解法二:
思想:滑动窗口,前后两个指针,不断调整前后两个指针的位置,大于target的时候,就该移动前面的指针,然后比较子序列的长度,否则后面的指针后移。
代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result=nums.size()+1;
        int sum=0;
        int i=0;
        int sub=0;
        for(int j=0;j<nums.size();j++)
        {
            sum+=nums[j];
            while(sum>=target){
                sub=j-i+1;
                if(sub<=result){result=sub;}
                sum-=nums[i];
                i++;
            }
        }
        if(result<=nums.size()){return result;}
        else{return 0;}
    }
};

904. 水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
示例:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:
1 <= fruits.length <= 105
0 <= fruits[i] < fruits.length

思想:
我的处理是在有三个及三个元素以上在进行排查。共四个指针,first就指向第一个篮子,second是第二个篮子,tmp是在第三个元素开始判断的指针,判断是否是前两个蓝色中的数,如果不是,first后移,second也后移。还有一个指针式p,用于检测是否是第一个篮子和第二个篮子的交替行为,因为比如3 3 3 1 2 1 1 2 3 3 4这类,如果不交替判断,会出现first指向index为3的位置,second指向index为4的位置,但实际second应该指向index为7的位置。c++
代码:

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int first=0;
        int second =1;
        int tmp=2;
        int result=0;
        int len=0;
        int p=second;
        if(fruits.size()==1){return 1;}
        if(fruits.size()==2){
            return 2;
        }

        for(;tmp<fruits.size();tmp++){
            if(fruits[tmp]!=fruits[second]&&fruits[tmp]!=fruits[first])
            {
                if(fruits[first]!=fruits[second]){
                    first=p;
                    second=tmp;
                }
                     second=tmp;        
            }
            if(fruits[p]!=fruits[tmp]){ //两个篮子的交替判断
                p=tmp;
            }
            len=tmp-first+1;
            if(len>=result){
                result=len;
            }
        }
        return result;
    }
};
<think>好的,用户让我介绍一下双指针法,并且用C++来演示。首先,我需要确认用户的需求是什么。可能他们正在学习算法,或者遇到了需要使用双指针的问题,比如数组、链表相关的题目。双指针法是一种常见的算法技巧,应用广泛,比如快慢指针、左右指针等。用户可能想了解基本概念、使用场景以及具体的代码实现。 接下来,我需要回忆双指针法的不同类型和应用场景。比如,快慢指针常用于检测链表中的环,或者找到链表的中间节点;左右指针常用于有序数组的两数之和、反转数组等;前后指针可能用于滑动窗口或者特定条件下的数组操作。要确保覆盖这些不同的类型,并给出相应的例子。 然后,用户希望使用C++来实现,所以我需要提供相应的代码示例。要确保代码简洁易懂,符合C++的语法规范。比如,链表的快慢指针检测环,可能需要定义一个链表的结构,然后实现快慢指针的移动。对于两数之和,可能需要使用左右指针向中间逼近,同时处理有序数组的情况。 同时,用户可能对时间复杂度和空间复杂度感兴趣,所以在每个示例后面加上复杂度分析会更好。这样用户能明白双指针法的优势,通常是O(n)的时间复杂度,而空间复杂度为O(1),因为只需要两个指针变量。 还需要考虑用户可能的疑问点。比如,为什么双指针法有效?如何确定指针移动的条件?这时候在解释每个示例时,需要简要说明原理,比如在有序数组中,左右指针如何根据当前和与目标值的比较来决定移动左指针还是右指针,从而减少不必要的遍历。 另外,用户可能对双指针法的适用问题类型不太清楚,所以需要总结适用场景,比如数组、链表、字符串处理等,并且提到常见的问题类型,如去重、反转、子数组等。这样用户遇到类似问题时可以联想到双指针法。 最后,要检查回答是否符合之前用户提供的系统级指令,比如数学公式是否用$...$包裹,独立公式是否用$$...$$,使用中文回答,结构清晰。比如在代码示例中的注释是否需要调整,或者是否需要添加数学表达式来解释某些算法步骤。 总结一下,回答的结构应该包括双指针法的概述、三种常见类型(快慢、左右、前后)、各自的C++示例及解释、复杂度分析、适用场景总结以及注意事项。确保每个部分都简明扼要,代码正确无误,并且用中文清晰表达。</think>### **双指针法(Two Pointers)详解(C++ 实现)** **双指针法**是一种通过两个指针协同遍历数据结构的算法技巧,主要用于**数组、链表、字符串**等线性结构的问题。其核心思想是通过指针的移动减少循环次数,将时间复杂度优化到 $O(n)$ 或更低。以下是具体分类及实现示例: --- ### **一、双指针法的三种常见类型** #### **1. 快慢指针(Fast-Slow Pointers)** - **用途**:检测循环、找中点、去重等。 - **示例**:检测链表是否有环 ```cpp struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; bool hasCycle(ListNode *head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; // 慢指针每次走1步 fast = fast->next->next; // 快指针每次走2步 if (slow == fast) return true; } return false; } ``` **时间复杂度**:$O(n)$,**空间复杂度**:$O(1)$ --- #### **2. 左右指针(Left-Right Pointers)** - **用途**:有序数组的两数之和、反转数组、回文判断等。 - **示例**:有序数组的两数之和(LeetCode 167) ```cpp vector<int> twoSum(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) { return {left + 1, right + 1}; // 题目要求索引从1开始 } else if (sum < target) { left++; // 和太小,左指针右移 } else { right--; // 和太大,右指针左移 } } return {}; } ``` **时间复杂度**:$O(n)$,**空间复杂度**:$O(1)$ --- #### **3. 前后指针(Sliding Window)** - **用途**:子数组/子串问题(如最小覆盖子串、最长无重复子串)。 - **示例**:移除数组中的重复项(LeetCode 26) ```cpp int removeDuplicates(vector<int>& nums) { if (nums.empty()) return 0; int slow = 0; // 慢指针指向唯一元素的位置 for (int fast = 1; fast < nums.size(); fast++) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; // 将新元素移动到慢指针后 } } return slow + 1; // 返回新长度 } ``` **时间复杂度**:$O(n)$,**空间复杂度**:$O(1)$ --- ### **二、双指针法的适用场景** 1. **有序数组**:如两数之和、合并有序数组。 2. **链表操作**:如环检测、交点查找。 3. **字符串处理**:如回文验证、子串匹配。 4. **滑动窗口**:如最长无重复子串、最小覆盖子串。 --- ### **三、使用双指针法的优势** - **降维优化**:将暴力法的 $O(n^2)$ 优化到 $O(n)$。 - **原地操作**:多数情况下无需额外空间(如反转数组)。 - **逻辑清晰**:通过指针移动直接体现算法逻辑。 --- ### **四、注意事项(C++ 实现)** 1. **指针边界**:确保指针不越界(如 `fast->next` 前检查 `fast` 是否为 `nullptr`)。 2. **循环条件**:左右指针注意 `while (left <= right)` 或 `<` 的选择。 3. **移动顺序**:快慢指针中,先移动指针再判断是否相遇。 --- ### **五、经典练习题** 1. 反转字符串(LeetCode 344) 2. 盛最多水的容器(LeetCode 11) 3. 三数之和(LeetCode 15) 4. 链表的中间节点(LeetCode 876) 如果需要具体问题的代码解析,可提供题目名称,我会给出详细实现!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值