【双指针问题】

1.分析

双指针技巧再分为两类,一类是**「快慢指针」,一类是「左右指针」**。
前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环;后者主要解决数组(或者字符串)中的问题,比如二分查找。

2.快慢指针

2.1 判断单向链表是否有环(Leetcode141)

两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

class Solution {
public:
    bool hasCycle(ListNode *head)
    {
        
        ListNode* fast=head;
        ListNode* slow=head;
        //若无环一定是快指针先遇到NULL
        while((fast!=NULL)&&(fast->next!=NULL))
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
                return true;
            
        }
        return false;
 
    }
};

2.2 返回单向链表环的起始位置

2.3 寻找链表的中点

LeetCode876. Middle of the Linked List
类似上面的思路,我们还可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。当链表的长度是奇数时,slow恰巧停在中点位置;如果长度是偶数,slow最终的位置是中间偏右。

class Solution {
public:
    ListNode* middleNode(ListNode* head) 
    {
        ListNode* slow=head;
        ListNode* fast=head;
        while((fast->next!=NULL)&&(fast->next->next!=NULL))
        {
            slow=slow->next;
            fast=fast->next->next;
            
        }
        if((fast->next!=NULL)&&(fast->next->next==NULL))
        return slow->next;
        return slow;
        
    }
};

2.4 寻找链表的倒数第n个元素

LeetCode19. Remove Nth Node From End of List
我们的思路还是使用快慢指针,让快指针先走n步,然后快慢指针开始同速前进。这样当快指针走到链表末尾null时,慢指针所在的位置就是倒数第n个链表节点(n不会超过链表长度)。
【注意】判断n步后,fast是否到头!!!若到头则删除第一个结点即可

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) 
    {
      ListNode* fast=head;
      ListNode* slow=head;
      while(n--)
      {
          fast=fast->next;
      }
      ListNode *save=NULL;
       //如果fast==NULL,说明要删除的为头结点
        if(fast==NULL)
        return head->next;
        
      while(fast!=NULL)
      {
          fast=fast->next;
          save=slow;
          slow=slow->next;
      }
      //此时slow指向需删除点
      save->next=slow->next;
      return head; 
    }
};

3.左右指针

3.1 二分查找

代码中left + (right - left) / 2就和(left + right) / 2的结果相同

class Solution {
public:
    int search(vector<int>& nums, int target)
    {
      int right=nums.size()-1;
    int left=0;
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]==target)
            {
                return mid;
            }
            else if(nums[mid]<target)
            {
                left=mid+1;
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
        }
        return -1;
    }
};

3.2 寻找合适的两数之和【LeetCode167】

只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节left和right可以调整sum的大小:

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int left=0;
        int right=numbers.size()-1;
        while(numbers[left]+numbers[right]!=target)
        {
            if(numbers[left]+numbers[right]<target)
            {
                left++;
            }
            else if((numbers[left]+numbers[right]>target))
                    {
                        right--;
                    }
        }
        vector<int> ans;
        ans.push_back(left+1);
        ans.push_back(right+1);
        return ans;
    }
};

3.3 滑动窗口

1、我们在字符串S中使用双指针中的左右指针技巧,初始化left = right = 0,把索引左闭右开区间[left, right)称为一个「窗口」。

2、我们先不断地增加right指针扩大窗口[left, right),直到窗口中的字符串符合要求(包含了T中的所有字符)。

3、此时,我们停止增加right,转而不断增加left指针缩小窗口[left, right),直到窗口中的字符串不再符合要求(不包含T中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到right到达字符串S的尽头。
【注意】最小窗口值是在收缩left指针时保存的,此时right值一定是符合要求留的最小可能右值,left在跳出while循环前最后一次也更新为最小值

class Solution {
public:
    string minWindow(string s, string t) 
    {
      //需要包含的字符集
      map<char,int> mp;
      //当前已扫描到的字符集
      map<char,int>win;
        for(int i=0;i<t.size();i++)
        {
            mp[t[i]]++;
        }
      int left=0;
      int right=0;
      int start=0;int len=INT_MAX;
        int keep=0;//成功包含的字符集数
        //先扩大右指针
      while(right<s.size())
      {
          char save=s[right];
          if(mp.find(save)!=mp.end())
          {
              win[save]++;
              if(win[save]==mp[save])
                  keep++;
              
          }
          //包含要求的字符串,开始收缩左侧边界
          while(keep==mp.size())
          {
              if(right-left<len)
                  { 
                   start=left;
                   len=right-left;
               }
            
             char save2=s[left];
              if(mp.find(save2)!=mp.end())
              {
                 
                  if(win[save2]==mp[save2])
                      keep--;
                   win[save2]--;
              }
              
              
              
              left++;
              
          }
              
          right++;
      }
      if(len==INT_MAX)
          return "";
      return s.substr(start,len+1);  
        
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值