双指针与滑动窗口的简单理解,与例题分析

目录

一、什么是双指针?什么是滑动窗口?

二、双指针实操

1.力扣27

2.141环形链表

三.滑窗实操

(1)长度最小的子数组

(2)904水果成篮

(3)76最小覆盖字串

总结


一、什么是双指针?什么是滑动窗口?

       双指针是我们在解决数组的相关题目时常用的方法,定义两个指针,以多种方式去处理一个数组。

常用的有:

1.快慢指针:通过设置两指针的初始位置,移动步长,可实现两指针一快一慢遍历数组。

2.头尾指针:一个定义在头,一个在尾,可以两个方向同时处理。

3.滑动窗口:两个指针一前一后形成一个窗口,不满足条件时前面的不停扩大窗口,满足时后面指针前进,缩小窗口,由可行解寻找最优解。

二、双指针实操

1.力扣27

     本题要求原地移除数组中的某些元素,不能使用额外空间,且元素顺序可以改变,无需考虑新数组后面的元素。

     思路:遇到val交换到原数组后面,即采用首尾指针。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) 
    {
       int n = nums.size();
       int i = 0;
       int j = n-1;
       int ans=0;
       while(i<=j)
       {
           if(nums[j]==val)
           {
              --j;
              ++ans;
              continue;
           }
           if(nums[i]==val)
           {
               swap(nums[i],nums[j]);
               --j;
               ++ans;
           }
           ++i;
       }
       return n-ans;
    }
};

相关题目:力扣977,11

2.141环形链表

判断是否成环,如果成环不断向下循环不会有nullptr,否则会找到nullptr。

思路:利用快慢指针,如是环路,终会相遇,不是环路就会找到nullptr;

class Solution {
public:
    bool hasCycle(ListNode *head) 
    {
        if(head==nullptr||head->next==nullptr)return false;
         ListNode*fast = head->next;
         ListNode*slow = head;
         while(fast->next != nullptr&&fast->next->next!=nullptr)
         {
             fast=fast->next->next;
             slow = slow->next;
             if(fast == slow)return true;
         }
         return false;
    }
};

相关题目 力扣 相交链表 

三.滑窗实操

(1)长度最小的子数组

暴力思路:循环遍历,每次满足条件记录长度,然后下次循环,但是其中存在冗余的操作,本题要使和为target,没必要每次从头开始求和,利用之前求过的和,下次循环就是把头去掉,加上尾部。

思路:

要求是连续的子数组,和大于等于target且长度最小,那么窗口的限制就是和大于等于target

右侧指针先扩大窗口满足条件,左侧收缩窗口直至不满足

int minSubArrayLen(int target, vector<int>& nums) 
    {
        int n= nums.size();
        int i=0;
        int j=0;
        int sum =0;
        int size = INT_MAX;
        while(j<=n-1)
        {         
            sum += nums[j];                 
            while(sum>=target)
            {        
                if(j-i+1<size)size=j-i+1;
               sum -= nums[i++];            
            }
             ++j;
        }
        if(size==INT_MAX)size=0;
        return size;
    }

(2)904水果成篮

思路:

本题提供两个篮子,没个篮子只能装一种,数量不限,那么窗口就是2即为篮子数,

向右扩大窗口,一旦满足条件,超过了篮子数量,左侧指针缩减窗口

本题有两个因素,树的种类和果子个数,因此采用哈希表存储

int totalFruit(vector<int>& fruits) 
    {
        unordered_map<int,int>un_map;
        int n = fruits.size();
        int i = 0;
        int j=0;
        int size = INT_MIN;
        for(;j<n;j++)
        {
            un_map[fruits[j]]++;
            while(un_map.size()>2)
            {
              int kind =  fruits[i++];
              if(--un_map[kind]==0)un_map.erase(kind);
            }
            size = max(size,j-i+1);
        }
        return size;
    }

(3)76最小覆盖字串

思路:

找出s中涵盖t的最小子串,窗口就是t,当涵盖所有字符时满足条件,左侧指针缩减窗口寻找最优解,本题涉及字母的类型以及个数,所以依旧选择哈希表存储,事先存储t,另一个存s,当t哈希表所有元素数目小于等于s中的 则满足条件。

class Solution {
public:
       unordered_map<char,int>map_t;
       unordered_map<char,int>map_s;

    bool check()
    {
        for(const auto &it:map_t)
        {
            if(map_s[it.first]<it.second)
            {
                return false;
            }
        }
        return true;
    }
    
    string minWindow(string s, string t) 
    {
       
       int b=0;
       int e=0;
       int lengh=INT_MAX;
       int ansl=0;

       for(int i=0;i<t.size();i++)
       {
           ++map_t[t[i]];
       }
       for(;e<s.size();e++)
       {
           if(map_t.find(s[e])!=map_t.end())
           ++map_s[s[e]];
           
               while(check())
               {
                   if(e-b+1<lengh)
                   {
                         lengh =e-b+1;
                         ansl=b;
                   }
                   
                   if(map_t.find(s[b])!=map_t.end())
                    --map_s[s[b]];
                    ++b;
               }
                     
       }      
           return lengh==INT_MAX?string():s.substr(ansl,lengh);
    }
};

(4)三数之和

思路:暴力解法,三层for循环,判断是否和为0,但是需要去重,我们需要现将数组排序,然后跳过重复的位置,我们使用一层循环确定一个数,剩下两个采取双指针的方式,这样三层变成两层循环。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        vector<vector<int>>ans;
        
        for(int i=0;i<nums.size();++i)
        {
            if(i>0&&nums[i]==nums[i-1])
            {
                continue;
            }
            int target=-nums[i];
            int right=nums.size()-1;
            
            for(int left=i+1;left<nums.size();++left)
            {
                if(left>i+1&&nums[left]==nums[left-1])
                {
                    continue;
                }
                 while(left<right&&nums[left]+nums[right]>target)
               {
               --right;
               }
               if(left==right)
               {
                   break;
               }
                if(nums[left]+nums[right]==target)
               {
                 ans.push_back({nums[i],nums[left],nums[right]});
               }
                
               
            }
           
        }
        return ans;
    }
};

 

总结

双指针一般分两种,同向和逆向,逆向多见于数组,同向多见于链表;

滑窗基本思路,找到窗口,选择合适的存储,先扩大窗口,满足条件,在缩小窗口由可行解找最优。

滑窗适合判断数组的连续子数组是否满足一定的条件,寻找最优解。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BlackMonkeyHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值